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

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

Issue 23199004: Support passing barback assets through phases in which they're unused. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Fix a logic error. 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
« no previous file with comments | « pkg/barback/lib/src/package_graph.dart ('k') | pkg/barback/test/package_graph/errors_test.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.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'; 10 import 'asset.dart';
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
49 final _inputs = new Map<AssetId, AssetNode>(); 49 final _inputs = new Map<AssetId, AssetNode>();
50 50
51 /// The transforms currently applicable to assets in [inputs], indexed by 51 /// The transforms currently applicable to assets in [inputs], indexed by
52 /// the ids of their primary inputs. 52 /// the ids of their primary inputs.
53 /// 53 ///
54 /// These are the transforms that have been "wired up": they represent a 54 /// These are the transforms that have been "wired up": they represent a
55 /// repeatable transformation of a single concrete set of inputs. "dart2js" 55 /// repeatable transformation of a single concrete set of inputs. "dart2js"
56 /// is a transformer. "dart2js on web/main.dart" is a transform. 56 /// is a transformer. "dart2js on web/main.dart" is a transform.
57 final _transforms = new Map<AssetId, Set<TransformNode>>(); 57 final _transforms = new Map<AssetId, Set<TransformNode>>();
58 58
59 /// Controllers for assets that aren't consumed by transforms in this phase.
60 ///
61 /// These assets are passed to the next phase unmodified. They need
62 /// intervening controllers to ensure that the outputs can be marked dirty
63 /// when determining whether transforms apply, and removed if they do.
64 final _passThroughControllers = new Map<AssetId, AssetNodeController>();
65
59 /// Futures that will complete once the transformers that can consume a given 66 /// Futures that will complete once the transformers that can consume a given
60 /// asset are determined. 67 /// asset are determined.
61 /// 68 ///
62 /// Whenever an asset is added or modified, we need to asynchronously 69 /// Whenever an asset is added or modified, we need to asynchronously
63 /// determine which transformers can use it as their primary input. We can't 70 /// determine which transformers can use it as their primary input. We can't
64 /// start processing until we know which transformers to run, and this allows 71 /// start processing until we know which transformers to run, and this allows
65 /// us to wait until we do. 72 /// us to wait until we do.
66 var _adjustTransformersFutures = new Map<AssetId, Future>(); 73 var _adjustTransformersFutures = new Map<AssetId, Future>();
67 74
68 /// New asset nodes that were added while [_adjustTransformers] was still 75 /// New asset nodes that were added while [_adjustTransformers] was still
69 /// being run on an old version of that asset. 76 /// being run on an old version of that asset.
70 var _pendingNewInputs = new Map<AssetId, AssetNode>(); 77 var _pendingNewInputs = new Map<AssetId, AssetNode>();
71 78
72 /// A map of output ids to the asset node outputs for those ids and the 79 /// A map of output ids to the asset node outputs for those ids and the
73 /// transforms that produced those asset nodes. 80 /// transforms that produced those asset nodes.
74 /// 81 ///
75 /// Usually there's only one node for a given output id. However, it's 82 /// Usually there's only one node for a given output id. However, it's
76 /// possible for multiple transformers in this phase to output an asset with 83 /// possible for multiple transformers to output an asset with the same id. In
77 /// the same id. In that case, the chronologically first output emitted is 84 /// that case, the chronologically first output emitted is passed forward. We
78 /// passed forward. We keep track of the other nodes so that if that output is 85 /// keep track of the other nodes so that if that output is removed, we know
79 /// removed, we know which asset to replace it with. 86 /// which asset to replace it with.
80 final _outputs = new Map<AssetId, Queue<AssetNode>>(); 87 final _outputs = new Map<AssetId, Queue<AssetNode>>();
81 88
82 /// A stream that emits an event whenever this phase becomes dirty and needs 89 /// A stream that emits an event whenever this phase becomes dirty and needs
83 /// to be run. 90 /// to be run.
84 /// 91 ///
85 /// This may emit events when the phase was already dirty or while processing 92 /// This may emit events when the phase was already dirty or while processing
86 /// transforms. Events are emitted synchronously to ensure that the dirty 93 /// transforms. Events are emitted synchronously to ensure that the dirty
87 /// state is thoroughly propagated as soon as any assets are changed. 94 /// state is thoroughly propagated as soon as any assets are changed.
88 Stream get onDirty => _onDirtyPool.stream; 95 Stream get onDirty => _onDirtyPool.stream;
89 final _onDirtyPool = new StreamPool.broadcast(); 96 final _onDirtyPool = new StreamPool.broadcast();
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after
156 // ignore it here but don't start processing the new input. That way 163 // ignore it here but don't start processing the new input. That way
157 // when [process] is called, the error will be piped through its 164 // when [process] is called, the error will be piped through its
158 // return value. 165 // return value.
159 }).catchError((e) { 166 }).catchError((e) {
160 // If our code above has a programmatic error, ensure it will be piped 167 // If our code above has a programmatic error, ensure it will be piped
161 // through [process] by putting it into [_adjustTransformersFutures]. 168 // through [process] by putting it into [_adjustTransformersFutures].
162 _adjustTransformersFutures[node.id] = new Future.error(e); 169 _adjustTransformersFutures[node.id] = new Future.error(e);
163 }); 170 });
164 } 171 }
165 172
166 /// Returns the input for this phase with the given [id], but only if that
167 /// input is known not to be consumed as a transformer's primary input.
168 ///
169 /// If the input is unavailable, or if the phase hasn't determined whether or
170 /// not any transformers will consume it as a primary input, null will be
171 /// returned instead. This means that the return value is guaranteed to always
172 /// be [AssetState.AVAILABLE].
173 AssetNode getUnconsumedInput(AssetId id) {
174 if (!_inputs.containsKey(id)) return null;
175
176 // If the asset has transforms, it's not unconsumed.
177 if (!_transforms[id].isEmpty) return null;
178
179 // If we're working on figuring out if the asset has transforms, we can't
180 // prove that it's unconsumed.
181 if (_adjustTransformersFutures.containsKey(id)) return null;
182
183 // The asset should be available. If it were removed, it wouldn't be in
184 // _inputs, and if it were dirty, it'd be in _adjustTransformersFutures.
185 assert(_inputs[id].state.isAvailable);
186 return _inputs[id];
187 }
188
189 /// Gets the asset node for an input [id]. 173 /// Gets the asset node for an input [id].
190 /// 174 ///
191 /// If an input with that ID cannot be found, returns null. 175 /// If an input with that ID cannot be found, returns null.
192 Future<AssetNode> getInput(AssetId id) { 176 Future<AssetNode> getInput(AssetId id) {
193 return newFuture(() { 177 return newFuture(() {
194 // TODO(rnystrom): Need to handle passthrough where an asset from a
195 // previous phase can be found.
196 if (id.package == cascade.package) return _inputs[id]; 178 if (id.package == cascade.package) return _inputs[id];
197 return cascade.graph.getAssetNode(id); 179 return cascade.graph.getAssetNode(id);
198 }); 180 });
199 } 181 }
200 182
201 /// Asynchronously determines which transformers can consume [node] as a 183 /// Asynchronously determines which transformers can consume [node] as a
202 /// primary input and creates transforms for them. 184 /// primary input and creates transforms for them.
203 /// 185 ///
204 /// This ensures that if [node] is modified or removed during or after the 186 /// This ensures that if [node] is modified or removed during or after the
205 /// time it takes to adjust its transformers, they're appropriately 187 /// time it takes to adjust its transformers, they're appropriately
206 /// re-adjusted. Its progress can be tracked in [_adjustTransformersFutures]. 188 /// re-adjusted. Its progress can be tracked in [_adjustTransformersFutures].
207 void _adjustTransformers(AssetNode node) { 189 void _adjustTransformers(AssetNode node) {
208 // Mark the phase as dirty. This may not actually end up creating any new 190 // Mark the phase as dirty. This may not actually end up creating any new
209 // transforms, but we want adding or removing a source asset to consistently 191 // transforms, but we want adding or removing a source asset to consistently
210 // kick off a build, even if that build does nothing. 192 // kick off a build, even if that build does nothing.
211 _onDirtyController.add(null); 193 _onDirtyController.add(null);
212 194
195 // If there's a pass-through for this node, mark it dirty while we figure
196 // out whether we need to add any transforms for it.
197 var controller = _passThroughControllers[node.id];
198 if (controller != null) controller.setDirty();
199
213 // Once the input is available, hook up transformers for it. If it changes 200 // Once the input is available, hook up transformers for it. If it changes
214 // while that's happening, try again. 201 // while that's happening, try again.
215 _adjustTransformersFutures[node.id] = node.tryUntilStable((asset) { 202 _adjustTransformersFutures[node.id] = node.tryUntilStable((asset) {
216 var oldTransformers = _transforms[node.id] 203 var oldTransformers = _transforms[node.id]
217 .map((transform) => transform.transformer).toSet(); 204 .map((transform) => transform.transformer).toSet();
218 205
219 return _removeStaleTransforms(asset) 206 return _removeStaleTransforms(asset)
220 .then((_) => _addFreshTransforms(node, oldTransformers)); 207 .then((_) => _addFreshTransforms(node, oldTransformers));
221 }).then((_) { 208 }).then((_) {
209 _adjustPassThrough(node);
210
222 // Now all the transforms are set up correctly and the asset is available 211 // Now all the transforms are set up correctly and the asset is available
223 // for the time being. Set up handlers for when the asset changes in the 212 // for the time being. Set up handlers for when the asset changes in the
224 // future. 213 // future.
225 node.onStateChange.first.then((state) { 214 node.onStateChange.first.then((state) {
226 if (state.isRemoved) { 215 if (state.isRemoved) {
227 _onDirtyController.add(null); 216 _onDirtyController.add(null);
228 _transforms.remove(node.id); 217 _transforms.remove(node.id);
218 var passThrough = _passThroughControllers.remove(node.id);
219 if (passThrough != null) passThrough.setRemoved();
229 } else { 220 } else {
230 _adjustTransformers(node); 221 _adjustTransformers(node);
231 } 222 }
232 }).catchError((e) { 223 }).catchError((e) {
233 _adjustTransformersFutures[node.id] = new Future.error(e); 224 _adjustTransformersFutures[node.id] = new Future.error(e);
234 }); 225 });
235 }).catchError((error) { 226 }).catchError((error) {
236 if (error is! AssetNotFoundException || error.id != node.id) throw error; 227 if (error is! AssetNotFoundException || error.id != node.id) throw error;
237 228
238 // If the asset is removed, [tryUntilStable] will throw an 229 // If the asset is removed, [tryUntilStable] will throw an
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after
284 // results. 275 // results.
285 return transformer.isPrimary(node.asset).then((isPrimary) { 276 return transformer.isPrimary(node.asset).then((isPrimary) {
286 if (!isPrimary) return; 277 if (!isPrimary) return;
287 var transform = new TransformNode(this, transformer, node); 278 var transform = new TransformNode(this, transformer, node);
288 _transforms[node.id].add(transform); 279 _transforms[node.id].add(transform);
289 _onDirtyPool.add(transform.onDirty); 280 _onDirtyPool.add(transform.onDirty);
290 }); 281 });
291 })); 282 }));
292 } 283 }
293 284
285 /// Adjust whether [node] is passed through the phase unmodified, based on
286 /// whether it's consumed by other transforms in this phase.
287 ///
288 /// If [node] was already passed-through, this will update the passed-through
289 /// value.
290 void _adjustPassThrough(AssetNode node) {
291 assert(node.state.isAvailable);
292
293 if (_transforms[node.id].isEmpty) {
294 var controller = _passThroughControllers[node.id];
295 if (controller != null) {
296 controller.setAvailable(node.asset);
297 } else {
298 _passThroughControllers[node.id] =
299 new AssetNodeController.available(node.asset, node.transform);
300 }
301 } else {
302 var controller = _passThroughControllers.remove(node.id);
303 if (controller != null) controller.setRemoved();
304 }
305 }
306
294 /// Processes this phase. 307 /// Processes this phase.
295 /// 308 ///
296 /// Returns a future that completes when processing is done. If there is 309 /// Returns a future that completes when processing is done. If there is
297 /// nothing to process, returns `null`. 310 /// nothing to process, returns `null`.
298 Future process() { 311 Future process() {
299 if (_adjustTransformersFutures.isEmpty) return _processTransforms(); 312 if (_adjustTransformersFutures.isEmpty) return _processTransforms();
300 return _waitForInputs().then((_) => _processTransforms()); 313 return _waitForInputs().then((_) => _processTransforms());
301 } 314 }
302 315
303 Future _waitForInputs() { 316 Future _waitForInputs() {
304 if (_adjustTransformersFutures.isEmpty) return new Future.value(); 317 if (_adjustTransformersFutures.isEmpty) return new Future.value();
305 return Future.wait(_adjustTransformersFutures.values) 318 return Future.wait(_adjustTransformersFutures.values)
306 .then((_) => _waitForInputs()); 319 .then((_) => _waitForInputs());
307 } 320 }
308 321
309 /// Applies all currently wired up and dirty transforms. 322 /// Applies all currently wired up and dirty transforms.
310 Future _processTransforms() { 323 Future _processTransforms() {
324 if (_next == null) return;
325
326 var newPassThroughs = _passThroughControllers.values
327 .map((controller) => controller.node)
328 .where((output) {
329 return !_outputs.containsKey(output.id) ||
330 !_outputs[output.id].contains(output);
331 }).toSet();
332
311 // Convert this to a list so we can safely modify _transforms while 333 // Convert this to a list so we can safely modify _transforms while
312 // iterating over it. 334 // iterating over it.
313 var dirtyTransforms = 335 var dirtyTransforms =
314 flatten(_transforms.values.map((transforms) => transforms.toList())) 336 flatten(_transforms.values.map((transforms) => transforms.toList()))
315 .where((transform) => transform.isDirty).toList(); 337 .where((transform) => transform.isDirty).toList();
316 if (dirtyTransforms.isEmpty) return null;
317 338
318 var collisions = new Set<AssetId>(); 339 if (dirtyTransforms.isEmpty && newPassThroughs.isEmpty) return null;
340
341 var collisions = _passAssetsThrough(newPassThroughs);
319 return Future.wait(dirtyTransforms.map((transform) { 342 return Future.wait(dirtyTransforms.map((transform) {
320 return transform.apply().then((outputs) { 343 return transform.apply().then((outputs) {
321 for (var output in outputs) { 344 for (var output in outputs) {
322 if (_outputs.containsKey(output.id)) { 345 if (_outputs.containsKey(output.id)) {
323 _outputs[output.id].add(output); 346 _outputs[output.id].add(output);
324 collisions.add(output.id); 347 collisions.add(output.id);
325 } else { 348 } else {
326 _outputs[output.id] = new Queue<AssetNode>.from([output]); 349 _outputs[output.id] = new Queue<AssetNode>.from([output]);
327 _next.addInput(output); 350 _next.addInput(output);
328 } 351 }
(...skipping 10 matching lines...) Expand all
339 // while another transform was running. 362 // while another transform was running.
340 if (_outputs[collision].length <= 1) continue; 363 if (_outputs[collision].length <= 1) continue;
341 cascade.reportError(new AssetCollisionException( 364 cascade.reportError(new AssetCollisionException(
342 _outputs[collision].where((asset) => asset.transform != null) 365 _outputs[collision].where((asset) => asset.transform != null)
343 .map((asset) => asset.transform.info), 366 .map((asset) => asset.transform.info),
344 collision)); 367 collision));
345 } 368 }
346 }); 369 });
347 } 370 }
348 371
372 /// Pass all new assets that aren't consumed by transforms through to the next
373 /// phase.
374 ///
375 /// Returns a set of asset ids that have collisions between new passed-through
376 /// assets and pre-existing transform outputs.
377 Set<AssetId> _passAssetsThrough(Set<AssetId> newPassThroughs) {
378 var collisions = new Set<AssetId>();
379 for (var output in newPassThroughs) {
380 if (_outputs.containsKey(output.id)) {
381 // There shouldn't be another pass-through asset with the same id.
382 assert(!_outputs[output.id].any((asset) => asset.transform == null));
383
384 _outputs[output.id].add(output);
385 collisions.add(output.id);
386 } else {
387 _outputs[output.id] = new Queue<AssetNode>.from([output]);
388 _next.addInput(output);
389 }
390
391 _handleOutputRemoval(output);
392 }
393 return collisions;
394 }
395
349 /// Properly resolve collisions when [output] is removed. 396 /// Properly resolve collisions when [output] is removed.
350 void _handleOutputRemoval(AssetNode output) { 397 void _handleOutputRemoval(AssetNode output) {
351 output.whenRemoved.then((_) { 398 output.whenRemoved.then((_) {
352 var assets = _outputs[output.id]; 399 var assets = _outputs[output.id];
353 if (assets.length == 1) { 400 if (assets.length == 1) {
354 assert(assets.single == output); 401 assert(assets.single == output);
355 _outputs.remove(output.id); 402 _outputs.remove(output.id);
356 return; 403 return;
357 } 404 }
358 405
(...skipping 16 matching lines...) Expand all
375 // Pump the event queue to ensure that the removal of the input triggers 422 // Pump the event queue to ensure that the removal of the input triggers
376 // a new build to which we can attach the error. 423 // a new build to which we can attach the error.
377 newFuture(() => cascade.reportError(new AssetCollisionException( 424 newFuture(() => cascade.reportError(new AssetCollisionException(
378 assets.where((asset) => asset.transform != null) 425 assets.where((asset) => asset.transform != null)
379 .map((asset) => asset.transform.info), 426 .map((asset) => asset.transform.info),
380 output.id))); 427 output.id)));
381 } 428 }
382 }); 429 });
383 } 430 }
384 } 431 }
OLDNEW
« no previous file with comments | « pkg/barback/lib/src/package_graph.dart ('k') | pkg/barback/test/package_graph/errors_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698