Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(283)

Side by Side Diff: pkg/barback/lib/src/transform_node.dart

Issue 227123007: Only run [declareOutputs] once for each asset/transformer pair. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: code review Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « pkg/barback/lib/src/transform.dart ('k') | pkg/barback/lib/src/transformer.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 'errors.dart'; 14 import 'errors.dart';
14 import 'lazy_transformer.dart'; 15 import 'lazy_transformer.dart';
15 import 'log.dart'; 16 import 'log.dart';
16 import 'phase.dart'; 17 import 'phase.dart';
17 import 'stream_pool.dart'; 18 import 'stream_pool.dart';
18 import 'transform.dart'; 19 import 'transform.dart';
19 import 'transformer.dart'; 20 import 'transformer.dart';
20 import 'utils.dart'; 21 import 'utils.dart';
21 22
22 /// 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
(...skipping 14 matching lines...) Expand all
37 /// A string describing the location of [this] in the transformer graph. 38 /// A string describing the location of [this] in the transformer graph.
38 final String _location; 39 final String _location;
39 40
40 /// The subscription to [primary]'s [AssetNode.onStateChange] stream. 41 /// The subscription to [primary]'s [AssetNode.onStateChange] stream.
41 StreamSubscription _primarySubscription; 42 StreamSubscription _primarySubscription;
42 43
43 /// The subscription to [phase]'s [Phase.onAsset] stream. 44 /// The subscription to [phase]'s [Phase.onAsset] stream.
44 StreamSubscription<AssetNode> _phaseSubscription; 45 StreamSubscription<AssetNode> _phaseSubscription;
45 46
46 /// Whether [this] is dirty and still has more processing to do. 47 /// Whether [this] is dirty and still has more processing to do.
47 bool get isDirty => !_state.isDone; 48 bool get isDirty => _state != _State.NOT_PRIMARY && _state != _State.APPLIED;
48 49
49 /// Whether [transformer] is lazy and this transform has yet to be forced. 50 /// Whether [transformer] is lazy and this transform has yet to be forced.
50 bool _isLazy; 51 bool _isLazy;
51 52
52 /// The subscriptions to each input's [AssetNode.onStateChange] stream. 53 /// The subscriptions to each input's [AssetNode.onStateChange] stream.
53 final _inputSubscriptions = new Map<AssetId, StreamSubscription>(); 54 final _inputSubscriptions = new Map<AssetId, StreamSubscription>();
54 55
55 /// The controllers for the asset nodes emitted by this node. 56 /// The controllers for the asset nodes emitted by this node.
56 final _outputControllers = new Map<AssetId, AssetNodeController>(); 57 final _outputControllers = new Map<AssetId, AssetNodeController>();
57 58
59 /// The ids of inputs the transformer tried and failed to read last time it
60 /// ran.
58 final _missingInputs = new Set<AssetId>(); 61 final _missingInputs = new Set<AssetId>();
59 62
60 /// The controller that's used to pass [primary] through [this] if it's not 63 /// The controller that's used to pass [primary] through [this] if it's not
61 /// consumed or overwritten. 64 /// consumed or overwritten.
62 /// 65 ///
63 /// This needs an intervening controller to ensure that the output can be 66 /// This needs an intervening controller to ensure that the output can be
64 /// marked dirty when determining whether [this] will consume or overwrite it, 67 /// marked dirty when determining whether [this] will consume or overwrite it,
65 /// and be marked removed if it does. [_passThroughController] will be null 68 /// and be marked removed if it does. [_passThroughController] will be null
66 /// if the asset is not being passed through. 69 /// if the asset is not being passed through.
67 AssetNodeController _passThroughController; 70 AssetNodeController _passThroughController;
(...skipping 14 matching lines...) Expand all
82 new StreamController<AssetNode>.broadcast(sync: true); 85 new StreamController<AssetNode>.broadcast(sync: true);
83 86
84 /// A stream that emits an event whenever this transform logs an entry. 87 /// A stream that emits an event whenever this transform logs an entry.
85 /// 88 ///
86 /// This is synchronous because error logs can cause the transform to fail, so 89 /// This is synchronous because error logs can cause the transform to fail, so
87 /// we need to ensure that their processing isn't delayed until after the 90 /// we need to ensure that their processing isn't delayed until after the
88 /// transform or build has finished. 91 /// transform or build has finished.
89 Stream<LogEntry> get onLog => _onLogPool.stream; 92 Stream<LogEntry> get onLog => _onLogPool.stream;
90 final _onLogPool = new StreamPool<LogEntry>.broadcast(); 93 final _onLogPool = new StreamPool<LogEntry>.broadcast();
91 94
95 /// A controller for log entries emitted by this node.
96 final _onLogController = new StreamController<LogEntry>.broadcast(sync: true);
97
92 /// The current state of [this]. 98 /// The current state of [this].
93 var _state = _TransformNodeState.PROCESSING; 99 var _state = _State.COMPUTING_IS_PRIMARY;
94 100
95 /// Whether [this] has been marked as removed. 101 /// Whether [this] has been marked as removed.
96 bool get _isRemoved => _onAssetController.isClosed; 102 bool get _isRemoved => _onAssetController.isClosed;
97 103
98 /// Whether the most recent run of this transform has declared that it 104 /// Whether the most recent run of this transform has declared that it
99 /// consumes the primary input. 105 /// consumes the primary input.
100 /// 106 ///
101 /// Defaults to `false`. This is not meaningful unless [_state] is 107 /// Defaults to `false`. This is not meaningful unless [_state] is
102 /// [_TransformNodeState.APPLIED]. 108 /// [_State.APPLIED].
103 bool _consumePrimary = false; 109 bool _consumePrimary = false;
104 110
111 /// The set of output ids that [transformer] declared it would emit.
112 ///
113 /// This is only non-null if [transformer] is a [DeclaringTransformer] and its
114 /// [declareOutputs] has been run successfully.
115 Set<AssetId> _declaredOutputs;
116
105 TransformNode(this.phase, Transformer transformer, this.primary, 117 TransformNode(this.phase, Transformer transformer, this.primary,
106 this._location) 118 this._location)
107 : transformer = transformer, 119 : transformer = transformer,
108 _isLazy = transformer is LazyTransformer { 120 _isLazy = transformer is LazyTransformer {
121 _onLogPool.add(_onLogController.stream);
122
109 _primarySubscription = primary.onStateChange.listen((state) { 123 _primarySubscription = primary.onStateChange.listen((state) {
110 if (state.isRemoved) { 124 if (state.isRemoved) {
111 remove(); 125 remove();
112 } else { 126 } else {
113 _dirty(primaryChanged: true); 127 _dirty();
114 } 128 }
115 }); 129 });
116 130
117 _phaseSubscription = phase.previous.onAsset.listen((node) { 131 _phaseSubscription = phase.previous.onAsset.listen((node) {
118 if (_missingInputs.contains(node.id)) _dirty(primaryChanged: false); 132 if (_missingInputs.contains(node.id)) _dirty();
119 }); 133 });
120 134
121 _process(); 135 _isPrimary();
122 } 136 }
123 137
124 /// The [TransformInfo] describing this node. 138 /// The [TransformInfo] describing this node.
125 /// 139 ///
126 /// [TransformInfo] is the publicly-visible representation of a transform 140 /// [TransformInfo] is the publicly-visible representation of a transform
127 /// node. 141 /// node.
128 TransformInfo get info => new TransformInfo(transformer, primary.id); 142 TransformInfo get info => new TransformInfo(transformer, primary.id);
129 143
130 /// Marks this transform as removed. 144 /// Marks this transform as removed.
131 /// 145 ///
132 /// 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
133 /// well. Normally this will be automatically done internally based on events 147 /// well. Normally this will be automatically done internally based on events
134 /// 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
135 /// valid even if its primary input still exists. 149 /// valid even if its primary input still exists.
136 void remove() { 150 void remove() {
151 _onLogController.close();
137 _onAssetController.close(); 152 _onAssetController.close();
138 _onDoneController.close(); 153 _onDoneController.close();
139 _primarySubscription.cancel(); 154 _primarySubscription.cancel();
140 _phaseSubscription.cancel(); 155 _phaseSubscription.cancel();
141 _clearInputSubscriptions(); 156 _clearInputSubscriptions();
142 _clearOutputs(); 157 _clearOutputs();
143 if (_passThroughController != null) { 158 if (_passThroughController != null) {
144 _passThroughController.setRemoved(); 159 _passThroughController.setRemoved();
145 _passThroughController = null; 160 _passThroughController = null;
146 } 161 }
147 } 162 }
148 163
149 /// If [transformer] is lazy, ensures that its concrete outputs will be 164 /// If [transformer] is lazy, ensures that its concrete outputs will be
150 /// generated. 165 /// generated.
151 void force() { 166 void force() {
152 // TODO(nweiz): we might want to have a timeout after which, if the 167 // TODO(nweiz): we might want to have a timeout after which, if the
153 // transform's outputs have gone unused, we switch it back to lazy mode. 168 // transform's outputs have gone unused, we switch it back to lazy mode.
154 if (!_isLazy) return; 169 if (!_isLazy) return;
155 _isLazy = false; 170 _isLazy = false;
156 _dirty(primaryChanged: false); 171 _dirty();
157 } 172 }
158 173
159 /// Marks this transform as dirty. 174 /// Marks this transform as dirty.
160 /// 175 ///
161 /// This causes all of the transform's outputs to be marked as dirty as well. 176 /// This causes all of the transform's outputs to be marked as dirty as well.
162 /// [primaryChanged] should be true if and only if [this] was set dirty 177 void _dirty() {
163 /// because [primary] changed. 178 if (_state == _State.NOT_PRIMARY) {
164 void _dirty({bool primaryChanged: false}) { 179 _emitPassThrough();
165 if (!primaryChanged && _state.isNotPrimary) return; 180 return;
181 }
182 if (_state == _State.COMPUTING_IS_PRIMARY || _isLazy) return;
166 183
167 if (_passThroughController != null) _passThroughController.setDirty(); 184 if (_passThroughController != null) _passThroughController.setDirty();
168 for (var controller in _outputControllers.values) { 185 for (var controller in _outputControllers.values) {
169 controller.setDirty(); 186 controller.setDirty();
170 } 187 }
171 188
172 if (_state.isDone) { 189 if (_state == _State.APPLIED) {
173 if (primaryChanged) { 190 _apply();
174 _process(); 191 } else {
175 } else { 192 _state = _State.NEEDS_APPLY;
176 _apply();
177 }
178 } else if (primaryChanged) {
179 _state = _TransformNodeState.NEEDS_IS_PRIMARY;
180 } else if (!_state.needsIsPrimary) {
181 _state = _TransformNodeState.NEEDS_APPLY;
182 } 193 }
183 } 194 }
184 195
185 /// Determines whether [primary] is primary for [transformer], and if so runs 196 /// Runs [transformer.isPrimary] and adjusts [this]'s state according to the
186 /// [transformer.apply]. 197 /// result.
187 void _process() { 198 ///
188 // Clear all the old input subscriptions. If an input is re-used, we'll 199 /// This will also run [_declareOutputs] and/or [_apply] as appropriate.
189 // re-subscribe. 200 void _isPrimary() {
190 _clearInputSubscriptions(); 201 syncFuture(() => transformer.isPrimary(primary.id))
191 _state = _TransformNodeState.PROCESSING; 202 .catchError((error, stackTrace) {
192 primary.whenAvailable((_) { 203 if (_isRemoved) return false;
193 _state = _TransformNodeState.PROCESSING;
194 return transformer.isPrimary(primary.asset);
195 }).catchError((error, stackTrace) {
196 // If the transform became dirty while processing, ignore any errors from
197 // it.
198 if (_state.needsIsPrimary || _isRemoved) return false;
199 204
200 // Catch all transformer errors and pipe them to the results stream. This 205 // Catch all transformer errors and pipe them to the results stream. This
201 // is so a broken transformer doesn't take down the whole graph. 206 // is so a broken transformer doesn't take down the whole graph.
202 phase.cascade.reportError(_wrapException(error, stackTrace)); 207 phase.cascade.reportError(_wrapException(error, stackTrace));
203 208
204 return false; 209 return false;
205 }).then((isPrimary) { 210 }).then((isPrimary) {
211 if (_isRemoved) return null;
212 if (isPrimary) {
213 return _declareOutputs().then((_) {
214 if (_isRemoved) return;
215 if (_isLazy) {
216 _state = _State.APPLIED;
217 _onDoneController.add(null);
218 } else {
219 _apply();
220 }
221 });
222 }
223
224 _emitPassThrough();
225 _state = _State.NOT_PRIMARY;
226 _onDoneController.add(null);
227 });
228 }
229
230 /// Runs [transform.declareOutputs] and emits the resulting assets as dirty
231 /// assets.
232 Future _declareOutputs() {
233 if (transformer is! DeclaringTransformer) return new Future.value();
234
235 var controller = new DeclaringTransformController(this);
236 return syncFuture(() {
237 return (transformer as DeclaringTransformer)
238 .declareOutputs(controller.transform);
239 }).then((_) {
206 if (_isRemoved) return; 240 if (_isRemoved) return;
207 if (_state.needsIsPrimary) { 241 if (controller.loggedError) return;
208 _process(); 242
209 } else if (isPrimary) { 243 _consumePrimary = controller.consumePrimary;
210 _apply(); 244 _declaredOutputs = controller.outputIds;
211 } else { 245 var invalidIds = _declaredOutputs
212 _clearOutputs(); 246 .where((id) => id.package != phase.cascade.package).toSet();
213 _emitPassThrough(); 247 for (var id in invalidIds) {
214 _state = _TransformNodeState.NOT_PRIMARY; 248 _declaredOutputs.remove(id);
215 _onDoneController.add(null); 249 // TODO(nweiz): report this as a warning rather than a failing error.
250 phase.cascade.reportError(new InvalidOutputException(info, id));
216 } 251 }
252
253 if (!_declaredOutputs.contains(primary.id)) _emitPassThrough();
254
255 for (var id in _declaredOutputs) {
256 var controller = transformer is LazyTransformer
257 ? new AssetNodeController.lazy(id, force, this)
258 : new AssetNodeController(id, this);
259 _outputControllers[id] = controller;
260 _onAssetController.add(controller.node);
261 }
262 }).catchError((error, stackTrace) {
263 if (_isRemoved) return;
264 phase.cascade.reportError(_wrapException(error, stackTrace));
217 }); 265 });
218 } 266 }
219 267
220 /// Applies this transform. 268 /// Applies this transform.
221 void _apply() { 269 void _apply() {
222 assert(!_onAssetController.isClosed); 270 assert(!_isRemoved && !_isLazy);
223 271
224 // Clear input subscriptions here as well as in [_process] because [_apply] 272 // Clear input subscriptions here as well as in [_process] because [_apply]
225 // may be restarted independently if only a secondary input changes. 273 // may be restarted independently if only a secondary input changes.
226 _clearInputSubscriptions(); 274 _clearInputSubscriptions();
227 _state = _TransformNodeState.PROCESSING; 275 _state = _State.APPLYING;
228 primary.whenAvailable((_) { 276 _runApply().then((hadError) {
229 if (_state.needsIsPrimary) return null;
230 _state = _TransformNodeState.PROCESSING;
231 // TODO(nweiz): If [transformer] is a [DeclaringTransformer] but not a
232 // [LazyTransformer], we can get some mileage out of doing a declarative
233 // first so we know how to hook up the assets.
234 if (_isLazy) return _declareLazy();
235 return _applyImmediate();
236 }).catchError((error, stackTrace) {
237 // If the transform became dirty while processing, ignore any errors from
238 // it.
239 if (!_state.isProcessing || _isRemoved) return false;
240
241 // Catch all transformer errors and pipe them to the results stream. This
242 // is so a broken transformer doesn't take down the whole graph.
243 phase.cascade.reportError(_wrapException(error, stackTrace));
244 return true;
245 }).then((hadError) {
246 if (_isRemoved) return; 277 if (_isRemoved) return;
247 278
248 if (_state.needsIsPrimary) { 279 if (_state == _State.NEEDS_APPLY) {
249 _process();
250 } else if (_state.needsApply) {
251 _apply(); 280 _apply();
252 } else { 281 return;
253 assert(_state.isProcessing); 282 }
254 if (hadError) { 283
255 _clearOutputs(); 284 assert(_state == _State.APPLYING);
285 if (hadError) {
286 _clearOutputs();
287 // If the transformer threw an error, we don't want to emit the
288 // pass-through asset in case it will be overwritten by the transformer.
289 // However, if the transformer declared that it wouldn't overwrite or
290 // consume the pass-through asset, we can safely emit it.
291 if (_declaredOutputs != null && !_consumePrimary &&
292 !_declaredOutputs.contains(primary.id)) {
293 _emitPassThrough();
294 } else {
256 _dontEmitPassThrough(); 295 _dontEmitPassThrough();
257 } 296 }
297 }
258 298
259 _state = _TransformNodeState.APPLIED; 299 _state = _State.APPLIED;
260 _onDoneController.add(null); 300 _onDoneController.add(null);
261 }
262 }); 301 });
263 } 302 }
264 303
265 /// Gets the asset for an input [id]. 304 /// Gets the asset for an input [id].
266 /// 305 ///
267 /// If an input with [id] cannot be found, throws an [AssetNotFoundException]. 306 /// If an input with [id] cannot be found, throws an [AssetNotFoundException].
268 Future<Asset> getInput(AssetId id) { 307 Future<Asset> getInput(AssetId id) {
269 return phase.previous.getOutput(id).then((node) { 308 return phase.previous.getOutput(id).then((node) {
270 // Throw if the input isn't found. This ensures the transformer's apply 309 // Throw if the input isn't found. This ensures the transformer's apply
271 // is exited. We'll then catch this and report it through the proper 310 // is exited. We'll then catch this and report it through the proper
272 // results stream. 311 // results stream.
273 if (node == null) { 312 if (node == null) {
274 _missingInputs.add(id); 313 _missingInputs.add(id);
275 throw new AssetNotFoundException(id); 314 throw new AssetNotFoundException(id);
276 } 315 }
277 316
278 _inputSubscriptions.putIfAbsent(node.id, () { 317 _inputSubscriptions.putIfAbsent(node.id, () {
279 return node.onStateChange.listen((_) => _dirty(primaryChanged: false)); 318 return node.onStateChange.listen((_) => _dirty());
280 }); 319 });
281 320
282 return node.asset; 321 return node.asset;
283 }); 322 });
284 } 323 }
285 324
286 /// Applies the transform so that it produces concrete (as opposed to lazy) 325 /// Run [Transformer.apply] as soon as [primary] is available.
287 /// outputs.
288 /// 326 ///
289 /// Returns whether or not the transformer logged an error. 327 /// Returns whether or not an error occurred while running the transformer.
290 Future<bool> _applyImmediate() { 328 Future<bool> _runApply() {
291 var transformController = new TransformController(this); 329 var transformController = new TransformController(this);
292 _onLogPool.add(transformController.onLog); 330 _onLogPool.add(transformController.onLog);
293 331
294 return syncFuture(() { 332 return primary.whenAvailable((_) {
295 return transformer.apply(transformController.transform); 333 if (_isRemoved) return null;
334 _state = _State.APPLYING;
335 return syncFuture(() => transformer.apply(transformController.transform));
296 }).then((_) { 336 }).then((_) {
297 if (!_state.isProcessing || _onAssetController.isClosed) return false; 337 if (_state == _State.NEEDS_APPLY || _isRemoved) return false;
298 if (transformController.loggedError) return true; 338 if (transformController.loggedError) return true;
339 _handleApplyResults(transformController);
340 return false;
341 }).catchError((error, stackTrace) {
342 // If the transform became dirty while processing, ignore any errors from
343 // it.
344 if (_state == _State.NEEDS_APPLY || _isRemoved) return false;
299 345
300 _consumePrimary = transformController.consumePrimary; 346 // Catch all transformer errors and pipe them to the results stream. This
301 347 // is so a broken transformer doesn't take down the whole graph.
302 var newOutputs = transformController.outputs; 348 phase.cascade.reportError(_wrapException(error, stackTrace));
303 // Any ids that are for a different package are invalid. 349 return true;
304 var invalidIds = newOutputs
305 .map((asset) => asset.id)
306 .where((id) => id.package != phase.cascade.package)
307 .toSet();
308 for (var id in invalidIds) {
309 newOutputs.removeId(id);
310 // TODO(nweiz): report this as a warning rather than a failing error.
311 phase.cascade.reportError(new InvalidOutputException(info, id));
312 }
313
314 // Remove outputs that used to exist but don't anymore.
315 for (var id in _outputControllers.keys.toList()) {
316 if (newOutputs.containsId(id)) continue;
317 _outputControllers.remove(id).setRemoved();
318 }
319
320 // Emit or stop emitting the pass-through asset between removing and
321 // adding outputs to ensure there are no collisions.
322 if (!newOutputs.containsId(primary.id)) {
323 _emitPassThrough();
324 } else {
325 _dontEmitPassThrough();
326 }
327
328 // Store any new outputs or new contents for existing outputs.
329 for (var asset in newOutputs) {
330 var controller = _outputControllers[asset.id];
331 if (controller != null) {
332 controller.setAvailable(asset);
333 } else {
334 var controller = new AssetNodeController.available(asset, this);
335 _outputControllers[asset.id] = controller;
336 _onAssetController.add(controller.node);
337 }
338 }
339
340 return false;
341 }); 350 });
342 } 351 }
343 352
344 /// Applies the transform in declarative mode so that it produces lazy 353 /// Handle the results of running [Transformer.apply].
345 /// outputs.
346 /// 354 ///
347 /// Returns whether or not the transformer logged an error. 355 /// [transformController] should be the controller for the [Transform] passed
348 Future<bool> _declareLazy() { 356 /// to [Transformer.apply].
349 var transformController = new DeclaringTransformController(this); 357 void _handleApplyResults(TransformController transformController) {
358 _consumePrimary = transformController.consumePrimary;
350 359
351 return syncFuture(() { 360 var newOutputs = transformController.outputs;
352 return (transformer as LazyTransformer) 361 // Any ids that are for a different package are invalid.
353 .declareOutputs(transformController.transform); 362 var invalidIds = newOutputs
354 }).then((_) { 363 .map((asset) => asset.id)
355 if (!_state.isProcessing || _onAssetController.isClosed) return false; 364 .where((id) => id.package != phase.cascade.package)
356 if (transformController.loggedError) return true; 365 .toSet();
366 for (var id in invalidIds) {
367 newOutputs.removeId(id);
368 // TODO(nweiz): report this as a warning rather than a failing error.
369 phase.cascade.reportError(new InvalidOutputException(info, id));
370 }
357 371
358 _consumePrimary = transformController.consumePrimary; 372 if (_declaredOutputs != null) {
373 var missingOutputs = _declaredOutputs.difference(
374 newOutputs.map((asset) => asset.id).toSet());
375 if (missingOutputs.isNotEmpty) {
376 _warn("This transformer didn't emit declared "
377 "${pluralize('output asset', missingOutputs.length)} "
378 "${toSentence(missingOutputs)}.");
379 }
380 }
359 381
360 var newIds = transformController.outputIds; 382 // Remove outputs that used to exist but don't anymore.
361 var invalidIds = 383 for (var id in _outputControllers.keys.toList()) {
362 newIds.where((id) => id.package != phase.cascade.package).toSet(); 384 if (newOutputs.containsId(id)) continue;
363 for (var id in invalidIds) { 385 _outputControllers.remove(id).setRemoved();
364 newIds.remove(id); 386 }
365 // TODO(nweiz): report this as a warning rather than a failing error. 387
366 phase.cascade.reportError(new InvalidOutputException(info, id)); 388 // Emit or stop emitting the pass-through asset between removing and
389 // adding outputs to ensure there are no collisions.
390 if (!_consumePrimary && !newOutputs.containsId(primary.id)) {
391 _emitPassThrough();
392 } else {
393 _dontEmitPassThrough();
394 }
395
396 // Store any new outputs or new contents for existing outputs.
397 for (var asset in newOutputs) {
398 var controller = _outputControllers[asset.id];
399 if (controller != null) {
400 controller.setAvailable(asset);
401 } else {
402 var controller = new AssetNodeController.available(asset, this);
403 _outputControllers[asset.id] = controller;
404 _onAssetController.add(controller.node);
367 } 405 }
368 406 }
369 // Remove outputs that used to exist but don't anymore.
370 for (var id in _outputControllers.keys.toList()) {
371 if (newIds.contains(id)) continue;
372 _outputControllers.remove(id).setRemoved();
373 }
374
375 // Emit or stop emitting the pass-through asset between removing and
376 // adding outputs to ensure there are no collisions.
377 if (!newIds.contains(primary.id)) {
378 _emitPassThrough();
379 } else {
380 _dontEmitPassThrough();
381 }
382
383 for (var id in newIds) {
384 var controller = _outputControllers[id];
385 if (controller != null) {
386 controller.setLazy(force);
387 } else {
388 var controller = new AssetNodeController.lazy(id, force, this);
389 _outputControllers[id] = controller;
390 _onAssetController.add(controller.node);
391 }
392 }
393
394 return false;
395 });
396 } 407 }
397 408
398 /// Cancels all subscriptions to secondary input nodes. 409 /// Cancels all subscriptions to secondary input nodes.
399 void _clearInputSubscriptions() { 410 void _clearInputSubscriptions() {
400 _missingInputs.clear(); 411 _missingInputs.clear();
401 for (var subscription in _inputSubscriptions.values) { 412 for (var subscription in _inputSubscriptions.values) {
402 subscription.cancel(); 413 subscription.cancel();
403 } 414 }
404 _inputSubscriptions.clear(); 415 _inputSubscriptions.clear();
405 } 416 }
406 417
407 /// Removes all output assets. 418 /// Removes all output assets.
408 void _clearOutputs() { 419 void _clearOutputs() {
409 // Remove all the previously-emitted assets. 420 // Remove all the previously-emitted assets.
410 for (var controller in _outputControllers.values) { 421 for (var controller in _outputControllers.values) {
411 controller.setRemoved(); 422 controller.setRemoved();
412 } 423 }
413 _outputControllers.clear(); 424 _outputControllers.clear();
414 } 425 }
415 426
416 /// Emit the pass-through asset if it's not being emitted already. 427 /// Emit the pass-through asset if it's not being emitted already.
417 void _emitPassThrough() { 428 void _emitPassThrough() {
418 assert(!_outputControllers.containsKey(primary.id)); 429 assert(!_outputControllers.containsKey(primary.id));
419 430
420 if (_consumePrimary) return; 431 if (_consumePrimary) return;
421 if (_passThroughController == null) { 432 if (_passThroughController == null) {
422 _passThroughController = new AssetNodeController.from(primary); 433 _passThroughController = new AssetNodeController.from(primary);
423 _onAssetController.add(_passThroughController.node); 434 _onAssetController.add(_passThroughController.node);
424 } else { 435 } else if (primary.state.isDirty) {
436 _passThroughController.setDirty();
437 } else if (!_passThroughController.node.state.isAvailable) {
425 _passThroughController.setAvailable(primary.asset); 438 _passThroughController.setAvailable(primary.asset);
426 } 439 }
427 } 440 }
428 441
429 /// Stop emitting the pass-through asset if it's being emitted already. 442 /// Stop emitting the pass-through asset if it's being emitted already.
430 void _dontEmitPassThrough() { 443 void _dontEmitPassThrough() {
431 if (_passThroughController == null) return; 444 if (_passThroughController == null) return;
432 _passThroughController.setRemoved(); 445 _passThroughController.setRemoved();
433 _passThroughController = null; 446 _passThroughController = null;
434 } 447 }
435 448
436 BarbackException _wrapException(error, StackTrace stackTrace) { 449 BarbackException _wrapException(error, StackTrace stackTrace) {
437 if (error is! AssetNotFoundException) { 450 if (error is! AssetNotFoundException) {
438 return new TransformerException(info, error, stackTrace); 451 return new TransformerException(info, error, stackTrace);
439 } else { 452 } else {
440 return new MissingInputException(info, error.id); 453 return new MissingInputException(info, error.id);
441 } 454 }
442 } 455 }
443 456
457 /// Emit a warning about the transformer on [id].
458 void _warn(String message) {
459 _onLogController.add(
460 new LogEntry(info, primary.id, LogLevel.WARNING, message, null));
461 }
462
444 String toString() => 463 String toString() =>
445 "transform node in $_location for $transformer on $primary"; 464 "transform node in $_location for $transformer on $primary";
446 } 465 }
447 466
448 /// The enum of states that [TransformNode] can be in. 467 /// The enum of states that [TransformNode] can be in.
449 class _TransformNodeState { 468 class _State {
450 /// The transform node is running [Transformer.isPrimary] or 469 /// The transform is running [Transformer.isPrimary].
451 /// [Transformer.apply] and doesn't need to re-run them.
452 /// 470 ///
453 /// If there are no external changes by the time the processing finishes, this 471 /// This is the initial state of the transformer. Once [Transformer.isPrimary]
454 /// will transition to [APPLIED] or [NOT_PRIMARY] depending on the result of 472 /// finishes running, this will transition to [APPLYING] if the input is
455 /// [Transformer.isPrimary]. If the primary input changes, this will 473 /// primary, or [NOT_PRIMARY] if it's not.
456 /// transition to [NEEDS_IS_PRIMARY]. If a secondary input changes, this will 474 static final COMPUTING_IS_PRIMARY = const _State._("computing isPrimary");
457 /// transition to [NEEDS_APPLY].
458 static final PROCESSING = const _TransformNodeState._("processing");
459 475
460 /// The transform is running [Transformer.isPrimary] or [Transformer.apply], 476 /// The transform is running [Transformer.apply].
461 /// but since it started the primary input changed, so it will need to re-run
462 /// [Transformer.isPrimary].
463 /// 477 ///
464 /// This will always transition to [Transformer.PROCESSING]. 478 /// If an input changes while in this state, it will transition to
465 static final NEEDS_IS_PRIMARY = 479 /// [NEEDS_APPLY]. If the [TransformNode] is still in this state when
466 const _TransformNodeState._("needs isPrimary"); 480 /// [Transformer.apply] finishes running, it will transition to [APPLIED].
481 static final APPLYING = const _State._("applying");
467 482
468 /// The transform is running [Transformer.apply], but since it started a 483 /// The transform is running [Transformer.apply], but an input changed after
469 /// secondary input changed, so it will need to re-run [Transformer.apply]. 484 /// it started, so it will need to re-run [Transformer.apply].
470 /// 485 ///
471 /// If there are no external changes by the time [Transformer.apply] finishes, 486 /// This will transition to [APPLYING] once [Transformer.apply] finishes
472 /// this will transition to [PROCESSING]. If the primary input changes, this 487 /// running.
473 /// will transition to [NEEDS_IS_PRIMARY]. 488 static final NEEDS_APPLY = const _State._("needs apply");
474 static final NEEDS_APPLY = const _TransformNodeState._("needs apply");
475 489
476 /// The transform has finished running [Transformer.apply], whether or not it 490 /// The transform has finished running [Transformer.apply], whether or not it
477 /// emitted an error. 491 /// emitted an error.
478 /// 492 ///
479 /// If the primary input or a secondary input changes, this will transition to 493 /// If the transformer is lazy, the [TransformNode] can also be in this state
480 /// [PROCESSING]. 494 /// when [Transformer.declareOutputs] has been run but [Transformer.apply] has
481 static final APPLIED = const _TransformNodeState._("applied"); 495 /// not.
496 ///
497 /// If an input changes, this will transition to [APPLYING].
498 static final APPLIED = const _State._("applied");
482 499
483 /// The transform has finished running [Transformer.isPrimary], which returned 500 /// The transform has finished running [Transformer.isPrimary], which returned
484 /// `false`. 501 /// `false`.
485 /// 502 ///
486 /// If the primary input changes, this will transition to [PROCESSING]. 503 /// This will never transition to another state.
487 static final NOT_PRIMARY = const _TransformNodeState._("not primary"); 504 static final NOT_PRIMARY = const _State._("not primary");
488
489 /// Whether [this] is [PROCESSING].
490 bool get isProcessing => this == _TransformNodeState.PROCESSING;
491
492 /// Whether [this] is [NEEDS_IS_PRIMARY].
493 bool get needsIsPrimary => this == _TransformNodeState.NEEDS_IS_PRIMARY;
494
495 /// Whether [this] is [NEEDS_APPLY].
496 bool get needsApply => this == _TransformNodeState.NEEDS_APPLY;
497
498 /// Whether [this] is [APPLIED].
499 bool get isApplied => this == _TransformNodeState.APPLIED;
500
501 /// Whether [this] is [NOT_PRIMARY].
502 bool get isNotPrimary => this == _TransformNodeState.NOT_PRIMARY;
503
504 /// Whether the transform has finished running [Transformer.isPrimary] and
505 /// [Transformer.apply].
506 ///
507 /// Specifically, whether [this] is [APPLIED] or [NOT_PRIMARY].
508 bool get isDone => isApplied || isNotPrimary;
509 505
510 final String name; 506 final String name;
511 507
512 const _TransformNodeState._(this.name); 508 const _State._(this.name);
513 509
514 String toString() => name; 510 String toString() => name;
515 } 511 }
OLDNEW
« no previous file with comments | « pkg/barback/lib/src/transform.dart ('k') | pkg/barback/lib/src/transformer.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698