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

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

Issue 261823008: Reorganize barback's source files. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: re-add barback/lib/src/internal_asset.dart Created 6 years, 7 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_logger.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
(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 }
OLDNEW
« no previous file with comments | « pkg/barback/lib/src/transform_logger.dart ('k') | pkg/barback/lib/src/transformer.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698