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

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

Issue 23363002: Factor out an input-handling class from Phase in barback. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Fix a library name Created 7 years, 4 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
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.phase; 5 library barback.phase;
6 6
7 import 'dart:async'; 7 import 'dart:async';
8 import 'dart:collection'; 8 import 'dart:collection';
9 9
10 import 'asset.dart';
11 import 'asset_cascade.dart'; 10 import 'asset_cascade.dart';
12 import 'asset_id.dart'; 11 import 'asset_id.dart';
13 import 'asset_node.dart'; 12 import 'asset_node.dart';
14 import 'errors.dart'; 13 import 'errors.dart';
14 import 'phase_input.dart';
15 import 'stream_pool.dart'; 15 import 'stream_pool.dart';
16 import 'transform_node.dart';
17 import 'transformer.dart'; 16 import 'transformer.dart';
18 import 'utils.dart'; 17 import 'utils.dart';
19 18
20 /// One phase in the ordered series of transformations in an [AssetCascade]. 19 /// One phase in the ordered series of transformations in an [AssetCascade].
21 /// 20 ///
22 /// Each phase can access outputs from previous phases and can in turn pass 21 /// Each phase can access outputs from previous phases and can in turn pass
23 /// outputs to later phases. Phases are processed strictly serially. All 22 /// outputs to later phases. Phases are processed strictly serially. All
24 /// transforms in a phase will be complete before moving on to the next phase. 23 /// transforms in a phase will be complete before moving on to the next phase.
25 /// Within a single phase, all transforms will be run in parallel. 24 /// Within a single phase, all transforms will be run in parallel.
26 /// 25 ///
27 /// Building can be interrupted between phases. For example, a source is added 26 /// Building can be interrupted between phases. For example, a source is added
28 /// which starts the background process. Sometime during, say, phase 2 (which 27 /// which starts the background process. Sometime during, say, phase 2 (which
29 /// is running asynchronously) that source is modified. When the process queue 28 /// is running asynchronously) that source is modified. When the process queue
30 /// goes to advance to phase 3, it will see that modification and start the 29 /// goes to advance to phase 3, it will see that modification and start the
31 /// waterfall from the beginning again. 30 /// waterfall from the beginning again.
32 class Phase { 31 class Phase {
33 /// The cascade that owns this phase. 32 /// The cascade that owns this phase.
34 final AssetCascade cascade; 33 final AssetCascade cascade;
35 34
36 /// The transformers that can access [inputs]. 35 /// The transformers that can access [inputs].
37 /// 36 ///
38 /// Their outputs will be available to the next phase. 37 /// Their outputs will be available to the next phase.
39 final Set<Transformer> _transformers; 38 final Set<Transformer> _transformers;
40 39
41 /// The inputs that are available for transforms in this phase to consume. 40 /// The inputs for this phase.
42 /// 41 ///
43 /// For the first phase, these will be the source assets. For all other 42 /// For the first phase, these will be the source assets. For all other
44 /// phases, they will be the outputs from the previous phase. 43 /// phases, they will be the outputs from the previous phase.
45 final _inputs = new Map<AssetId, AssetNode>(); 44 final _inputs = new Map<AssetId, PhaseInput>();
46
47 /// The transforms currently applicable to assets in [inputs], indexed by
48 /// the ids of their primary inputs.
49 ///
50 /// These are the transforms that have been "wired up": they represent a
51 /// repeatable transformation of a single concrete set of inputs. "dart2js"
52 /// is a transformer. "dart2js on web/main.dart" is a transform.
53 final _transforms = new Map<AssetId, Set<TransformNode>>();
54
55 /// Controllers for assets that aren't consumed by transforms in this phase.
56 ///
57 /// These assets are passed to the next phase unmodified. They need
58 /// intervening controllers to ensure that the outputs can be marked dirty
59 /// when determining whether transforms apply, and removed if they do.
60 final _passThroughControllers = new Map<AssetId, AssetNodeController>();
61
62 /// Futures that will complete once the transformers that can consume a given
63 /// asset are determined.
64 ///
65 /// Whenever an asset is added or modified, we need to asynchronously
66 /// determine which transformers can use it as their primary input. We can't
67 /// start processing until we know which transformers to run, and this allows
68 /// us to wait until we do.
69 var _adjustTransformersFutures = new Map<AssetId, Future>();
70
71 /// New asset nodes that were added while [_adjustTransformers] was still
72 /// being run on an old version of that asset.
73 var _pendingNewInputs = new Map<AssetId, AssetNode>();
74 45
75 /// A map of output ids to the asset node outputs for those ids and the 46 /// A map of output ids to the asset node outputs for those ids and the
76 /// transforms that produced those asset nodes. 47 /// transforms that produced those asset nodes.
77 /// 48 ///
78 /// Usually there's only one node for a given output id. However, it's 49 /// Usually there's only one node for a given output id. However, it's
79 /// possible for multiple transformers to output an asset with the same id. In 50 /// possible for multiple transformers to output an asset with the same id. In
80 /// that case, the chronologically first output emitted is passed forward. We 51 /// that case, the chronologically first output emitted is passed forward. We
81 /// keep track of the other nodes so that if that output is removed, we know 52 /// keep track of the other nodes so that if that output is removed, we know
82 /// which asset to replace it with. 53 /// which asset to replace it with.
83 final _outputs = new Map<AssetId, Queue<AssetNode>>(); 54 final _outputs = new Map<AssetId, Queue<AssetNode>>();
84 55
85 /// A stream that emits an event whenever this phase becomes dirty and needs 56 /// A stream that emits an event whenever this phase becomes dirty and needs
86 /// to be run. 57 /// to be run.
87 /// 58 ///
88 /// This may emit events when the phase was already dirty or while processing 59 /// This may emit events when the phase was already dirty or while processing
89 /// transforms. Events are emitted synchronously to ensure that the dirty 60 /// transforms. Events are emitted synchronously to ensure that the dirty
90 /// state is thoroughly propagated as soon as any assets are changed. 61 /// state is thoroughly propagated as soon as any assets are changed.
91 Stream get onDirty => _onDirtyPool.stream; 62 Stream get onDirty => _onDirtyPool.stream;
92 final _onDirtyPool = new StreamPool.broadcast(); 63 final _onDirtyPool = new StreamPool.broadcast();
93 64
94 /// A controller whose stream feeds into [_onDirtyPool]. 65 /// A controller whose stream feeds into [_onDirtyPool].
95 /// 66 ///
96 /// This is used whenever an input is added, changed, or removed. It's 67 /// This is used whenever an input is added or transforms are changed.
97 /// sometimes redundant with the events collected from [_transforms], but this
98 /// stream is necessary for new and removed inputs, and the transform stream
99 /// is necessary for modified secondary inputs.
100 final _onDirtyController = new StreamController.broadcast(sync: true); 68 final _onDirtyController = new StreamController.broadcast(sync: true);
101 69
102 /// The phase after this one. 70 /// The phase after this one.
103 /// 71 ///
104 /// Outputs from this phase will be passed to it. 72 /// Outputs from this phase will be passed to it.
105 Phase get next => _next; 73 Phase get next => _next;
106 Phase _next; 74 Phase _next;
107 75
108 /// Returns all currently-available output assets for this phase. 76 /// Returns all currently-available output assets for this phase.
109 AssetSet get availableOutputs { 77 AssetSet get availableOutputs {
(...skipping 12 matching lines...) Expand all
122 /// 90 ///
123 /// [node] doesn't have to be [AssetState.AVAILABLE]. Once it is, the phase 91 /// [node] doesn't have to be [AssetState.AVAILABLE]. Once it is, the phase
124 /// will automatically begin determining which transforms can consume it as a 92 /// will automatically begin determining which transforms can consume it as a
125 /// primary input. The transforms themselves won't be applied until [process] 93 /// primary input. The transforms themselves won't be applied until [process]
126 /// is called, however. 94 /// is called, however.
127 /// 95 ///
128 /// This should only be used for brand-new assets or assets that have been 96 /// This should only be used for brand-new assets or assets that have been
129 /// removed and re-created. The phase will automatically handle updated assets 97 /// removed and re-created. The phase will automatically handle updated assets
130 /// using the [AssetNode.onStateChange] stream. 98 /// using the [AssetNode.onStateChange] stream.
131 void addInput(AssetNode node) { 99 void addInput(AssetNode node) {
132 // We remove [node.id] from [inputs] as soon as the node is removed rather 100 if (_inputs.containsKey(node.id)) _inputs[node.id].remove();
133 // than at the same time [node.id] is removed from [_transforms] so we don't
134 // have to wait on [_adjustTransformers]. It's important that [inputs] is
135 // always up-to-date so that the [AssetCascade] can look there for available
136 // assets.
137 _inputs[node.id] = node;
138 node.whenRemoved.then((_) => _inputs.remove(node.id));
139 101
140 if (!_adjustTransformersFutures.containsKey(node.id)) { 102 _inputs[node.id] = new PhaseInput(this, node, _transformers);
141 _transforms[node.id] = new Set<TransformNode>(); 103 _inputs[node.id].input.whenRemoved.then((_) => _inputs.remove(node.id));
142 _adjustTransformers(node); 104 _onDirtyPool.add(_inputs[node.id].onDirty);
143 return; 105 _onDirtyController.add(null);
144 }
145
146 // If an input is added while the same input is still being processed,
147 // that means that the asset was removed and recreated while
148 // [_adjustTransformers] was being run on the old value. We have to wait
149 // until that finishes, then run it again on whatever the newest version
150 // of that asset is.
151
152 // We may already be waiting for the existing [_adjustTransformers] call to
153 // finish. If so, all we need to do is change the node that will be loaded
154 // after it completes.
155 var containedKey = _pendingNewInputs.containsKey(node.id);
156 _pendingNewInputs[node.id] = node;
157 if (containedKey) return;
158
159 // If we aren't already waiting, start doing so.
160 _adjustTransformersFutures[node.id].then((_) {
161 assert(!_adjustTransformersFutures.containsKey(node.id));
162 assert(_pendingNewInputs.containsKey(node.id));
163 _transforms[node.id] = new Set<TransformNode>();
164 _adjustTransformers(_pendingNewInputs.remove(node.id));
165 }, onError: (_) {
166 // If there was a programmatic error while processing the old input,
167 // we don't want to just ignore it; it may have left the system in an
168 // inconsistent state. We also don't want to top-level it, so we
169 // ignore it here but don't start processing the new input. That way
170 // when [process] is called, the error will be piped through its
171 // return value.
172 }).catchError((e) {
173 // If our code above has a programmatic error, ensure it will be piped
174 // through [process] by putting it into [_adjustTransformersFutures].
175 _adjustTransformersFutures[node.id] = new Future.error(e);
176 });
177 } 106 }
178 107
179 /// Gets the asset node for an input [id]. 108 /// Gets the asset node for an input [id].
180 /// 109 ///
181 /// If an input with that ID cannot be found, returns null. 110 /// If an input with that ID cannot be found, returns null.
182 Future<AssetNode> getInput(AssetId id) { 111 Future<AssetNode> getInput(AssetId id) {
183 return newFuture(() { 112 return newFuture(() {
184 if (id.package == cascade.package) return _inputs[id]; 113 if (id.package != cascade.package) return cascade.graph.getAssetNode(id);
185 return cascade.graph.getAssetNode(id); 114 if (_inputs.containsKey(id)) return _inputs[id].input;
115 return null;
186 }); 116 });
187 } 117 }
188 118
189 /// Gets the asset node for an output [id]. 119 /// Gets the asset node for an output [id].
190 /// 120 ///
191 /// If an output with that ID cannot be found, returns null. 121 /// If an output with that ID cannot be found, returns null.
192 Future<AssetNode> getOutput(AssetId id) { 122 Future<AssetNode> getOutput(AssetId id) {
193 return newFuture(() { 123 return newFuture(() {
194 if (id.package != cascade.package) return cascade.graph.getAssetNode(id); 124 if (id.package != cascade.package) return cascade.graph.getAssetNode(id);
195 if (!_outputs.containsKey(id)) return null; 125 if (!_outputs.containsKey(id)) return null;
196 return _outputs[id].first; 126 return _outputs[id].first;
197 }); 127 });
198 } 128 }
199 129
200 /// Set this phase's transformers to [transformers]. 130 /// Set this phase's transformers to [transformers].
201 void updateTransformers(Iterable<Transformer> transformers) { 131 void updateTransformers(Iterable<Transformer> transformers) {
202 _onDirtyController.add(null); 132 _onDirtyController.add(null);
203 133 _transformers.clear();
204 var newTransformers = transformers.toSet(); 134 _transformers.addAll(transformers);
205 var oldTransformers = _transformers.toSet(); 135 for (var input in _inputs.values) {
206 for (var removedTransformer in 136 input.updateTransformers(transformers);
207 oldTransformers.difference(newTransformers)) {
208 _transformers.remove(removedTransformer);
209
210 // Remove old transforms for which [removedTransformer] was a transformer.
211 for (var id in _inputs.keys) {
212 // If the transformers are being adjusted for [id], it will
213 // automatically pick up on [removedTransformer] being gone.
214 if (_adjustTransformersFutures.containsKey(id)) continue;
215
216 _transforms[id].removeWhere((transform) {
217 if (transform.transformer != removedTransformer) return false;
218 transform.remove();
219 return true;
220 });
221
222 if (!_transforms[id].isEmpty) continue;
223 _passThroughControllers.putIfAbsent(id, () {
224 return new AssetNodeController.available(
225 _inputs[id].asset, _inputs[id].transform);
226 });
227 }
228 } 137 }
229
230 var brandNewTransformers = newTransformers.difference(oldTransformers);
231 if (brandNewTransformers.isEmpty) return;
232 brandNewTransformers.forEach(_transformers.add);
233
234 // If there are any new transformers, start re-adjusting the transforms for
235 // all inputs so we pick up which inputs the new transformers apply to.
236 _inputs.forEach((id, node) {
237 if (_adjustTransformersFutures.containsKey(id)) return;
238 _adjustTransformers(node);
239 });
240 } 138 }
241 139
242 /// Add a new phase after this one with [transformers]. 140 /// Add a new phase after this one with [transformers].
243 /// 141 ///
244 /// This may only be called on a phase with no phase following it. 142 /// This may only be called on a phase with no phase following it.
245 Phase addPhase(Iterable<Transformer> transformers) { 143 Phase addPhase(Iterable<Transformer> transformers) {
246 assert(_next == null); 144 assert(_next == null);
247 _next = new Phase(cascade, transformers); 145 _next = new Phase(cascade, transformers);
248 for (var outputs in _outputs.values) { 146 for (var outputs in _outputs.values) {
249 _next.addInput(outputs.first); 147 _next.addInput(outputs.first);
250 } 148 }
251 return _next; 149 return _next;
252 } 150 }
253 151
254 /// Asynchronously determines which transformers can consume [node] as a
255 /// primary input and creates transforms for them.
256 ///
257 /// This ensures that if [node] is modified or removed during or after the
258 /// time it takes to adjust its transformers, they're appropriately
259 /// re-adjusted. Its progress can be tracked in [_adjustTransformersFutures].
260 void _adjustTransformers(AssetNode node) {
261 // Mark the phase as dirty. This may not actually end up creating any new
262 // transforms, but we want adding or removing a source asset to consistently
263 // kick off a build, even if that build does nothing.
264 _onDirtyController.add(null);
265
266 // If there's a pass-through for this node, mark it dirty while we figure
267 // out whether we need to add any transforms for it.
268 var controller = _passThroughControllers[node.id];
269 if (controller != null) controller.setDirty();
270
271 // Once the input is available, hook up transformers for it. If it changes
272 // while that's happening, try again.
273 _adjustTransformersFutures[node.id] = _tryUntilStable(node,
274 (asset, transformers) {
275 var oldTransformers = _transforms[node.id]
276 .map((transform) => transform.transformer).toSet();
277
278 return _removeStaleTransforms(asset, transformers).then((_) =>
279 _addFreshTransforms(node, transformers, oldTransformers));
280 }).then((_) {
281 _adjustPassThrough(node);
282
283 // Now all the transforms are set up correctly and the asset is available
284 // for the time being. Set up handlers for when the asset changes in the
285 // future.
286 node.onStateChange.first.then((state) {
287 if (state.isRemoved) {
288 _onDirtyController.add(null);
289 _transforms.remove(node.id);
290 var passThrough = _passThroughControllers.remove(node.id);
291 if (passThrough != null) passThrough.setRemoved();
292 } else {
293 _adjustTransformers(node);
294 }
295 }).catchError((e) {
296 _adjustTransformersFutures[node.id] = new Future.error(e);
297 });
298 }).catchError((error) {
299 if (error is! AssetNotFoundException || error.id != node.id) throw error;
300
301 // If the asset is removed, [tryUntilStable] will throw an
302 // [AssetNotFoundException]. In that case, just remove all transforms for
303 // the node, and its pass-through.
304 _transforms.remove(node.id);
305 var passThrough = _passThroughControllers.remove(node.id);
306 if (passThrough != null) passThrough.setRemoved();
307 }).whenComplete(() {
308 _adjustTransformersFutures.remove(node.id);
309 });
310
311 // Don't top-level errors coming from the input processing. Any errors will
312 // eventually be piped through [process]'s returned Future.
313 _adjustTransformersFutures[node.id].catchError((_) {});
314 }
315
316 // Remove any old transforms that used to have [asset] as a primary asset but
317 // no longer apply to its new contents.
318 Future _removeStaleTransforms(Asset asset, Set<Transformer> transformers) {
319 return Future.wait(_transforms[asset.id].map((transform) {
320 return newFuture(() {
321 if (!transformers.contains(transform.transformer)) return false;
322
323 // TODO(rnystrom): Catch all errors from isPrimary() and redirect to
324 // results.
325 return transform.transformer.isPrimary(asset);
326 }).then((isPrimary) {
327 if (isPrimary) return;
328 _transforms[asset.id].remove(transform);
329 _onDirtyPool.remove(transform.onDirty);
330 transform.remove();
331 });
332 }));
333 }
334
335 // Add new transforms for transformers that consider [node]'s asset to be a
336 // primary input.
337 //
338 // [oldTransformers] is the set of transformers for which there were
339 // transforms that had [node] as a primary input prior to this. They don't
340 // need to be checked, since their transforms were removed or preserved in
341 // [_removeStaleTransforms].
342 Future _addFreshTransforms(AssetNode node, Set<Transformer> transformers,
343 Set<Transformer> oldTransformers) {
344 return Future.wait(transformers.map((transformer) {
345 if (oldTransformers.contains(transformer)) return new Future.value();
346
347 // If the asset is unavailable, the results of this [_adjustTransformers]
348 // run will be discarded, so we can just short-circuit.
349 if (node.asset == null) return new Future.value();
350
351 // We can safely access [node.asset] here even though it might have
352 // changed since (as above) if it has, [_adjustTransformers] will just be
353 // re-run.
354 // TODO(rnystrom): Catch all errors from isPrimary() and redirect to
355 // results.
356 return transformer.isPrimary(node.asset).then((isPrimary) {
357 if (!isPrimary) return;
358 var transform = new TransformNode(this, transformer, node);
359 _transforms[node.id].add(transform);
360 _onDirtyPool.add(transform.onDirty);
361 });
362 }));
363 }
364
365 /// Adjust whether [node] is passed through the phase unmodified, based on
366 /// whether it's consumed by other transforms in this phase.
367 ///
368 /// If [node] was already passed-through, this will update the passed-through
369 /// value.
370 void _adjustPassThrough(AssetNode node) {
371 assert(node.state.isAvailable);
372
373 if (_transforms[node.id].isEmpty) {
374 var controller = _passThroughControllers[node.id];
375 if (controller != null) {
376 controller.setAvailable(node.asset);
377 } else {
378 _passThroughControllers[node.id] =
379 new AssetNodeController.available(node.asset, node.transform);
380 }
381 } else {
382 var controller = _passThroughControllers.remove(node.id);
383 if (controller != null) controller.setRemoved();
384 }
385 }
386
387 /// Like [AssetNode.tryUntilStable], but also re-runs [callback] if this
388 /// phase's transformers are modified.
389 Future _tryUntilStable(AssetNode node,
390 Future callback(Asset asset, Set<Transformer> transformers)) {
391 var oldTransformers;
392 return node.tryUntilStable((asset) {
393 oldTransformers = _transformers.toSet();
394 return callback(asset, _transformers);
395 }).then((result) {
396 if (setEquals(oldTransformers, _transformers)) return result;
397 return _tryUntilStable(node, callback);
398 });
399 }
400
401 /// Processes this phase. 152 /// Processes this phase.
402 /// 153 ///
403 /// Returns a future that completes when processing is done. If there is 154 /// Returns a future that completes when processing is done. If there is
404 /// nothing to process, returns `null`. 155 /// nothing to process, returns `null`.
405 Future process() { 156 Future process() {
406 if (_adjustTransformersFutures.isEmpty) return _processTransforms(); 157 if (!_inputs.values.any((input) => input.isDirty)) return null;
407 return _waitForInputs().then((_) => _processTransforms());
408 }
409 158
410 Future _waitForInputs() { 159 return Future.wait(_inputs.values.map((input) {
411 if (_adjustTransformersFutures.isEmpty) return new Future.value(); 160 if (!input.isDirty) return new Future.value(new Set());
412 return Future.wait(_adjustTransformersFutures.values) 161 return input.process().then((outputs) {
413 .then((_) => _waitForInputs()); 162 return outputs.where(_addOutput).map((output) => output.id).toSet();
414 }
415
416 /// Applies all currently wired up and dirty transforms.
417 Future _processTransforms() {
418 var newPassThroughs = _passThroughControllers.values
419 .map((controller) => controller.node)
420 .where((output) {
421 return !_outputs.containsKey(output.id) ||
422 !_outputs[output.id].contains(output);
423 }).toSet();
424
425 // Convert this to a list so we can safely modify _transforms while
426 // iterating over it.
427 var dirtyTransforms =
428 flatten(_transforms.values.map((transforms) => transforms.toList()))
429 .where((transform) => transform.isDirty).toList();
430
431 if (dirtyTransforms.isEmpty && newPassThroughs.isEmpty) return null;
432
433 var collisions = new Set<AssetId>();
434 for (var output in newPassThroughs) {
435 if (_addOutput(output)) collisions.add(output.id);
436 }
437
438 return Future.wait(dirtyTransforms.map((transform) {
439 return transform.apply().then((outputs) {
440 for (var output in outputs) {
441 if (_addOutput(output)) collisions.add(output.id);
442 }
443 }); 163 });
444 })).then((_) { 164 })).then((collisionsList) {
445 // Report collisions in a deterministic order. 165 // Report collisions in a deterministic order.
446 collisions = collisions.toList(); 166 var collisions = unionAll(collisionsList).toList();
447 collisions.sort((a, b) => a.compareTo(b)); 167 collisions.sort((a, b) => a.compareTo(b));
448 for (var collision in collisions) { 168 for (var collision in collisions) {
449 // Ensure that there's still a collision. It's possible it was resolved 169 // Ensure that there's still a collision. It's possible it was resolved
450 // while another transform was running. 170 // while another transform was running.
451 if (_outputs[collision].length <= 1) continue; 171 if (_outputs[collision].length <= 1) continue;
452 cascade.reportError(new AssetCollisionException( 172 cascade.reportError(new AssetCollisionException(
453 _outputs[collision].where((asset) => asset.transform != null) 173 _outputs[collision].where((asset) => asset.transform != null)
454 .map((asset) => asset.transform.info), 174 .map((asset) => asset.transform.info),
455 collision)); 175 collision));
456 } 176 }
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
503 // Pump the event queue to ensure that the removal of the input triggers 223 // Pump the event queue to ensure that the removal of the input triggers
504 // a new build to which we can attach the error. 224 // a new build to which we can attach the error.
505 newFuture(() => cascade.reportError(new AssetCollisionException( 225 newFuture(() => cascade.reportError(new AssetCollisionException(
506 assets.where((asset) => asset.transform != null) 226 assets.where((asset) => asset.transform != null)
507 .map((asset) => asset.transform.info), 227 .map((asset) => asset.transform.info),
508 output.id))); 228 output.id)));
509 } 229 }
510 }); 230 });
511 } 231 }
512 } 232 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698