@@ -56,6 +56,13 @@ class Phase {
5656 /// is a transformer. "dart2js on web/main.dart" is a transform.
5757 final _transforms = new Map <AssetId , Set <TransformNode >>();
5858
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+
5966 /// Futures that will complete once the transformers that can consume a given
6067 /// asset are determined.
6168 ///
@@ -73,10 +80,10 @@ class Phase {
7380 /// transforms that produced those asset nodes.
7481 ///
7582 /// 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
77- /// the same id. In that case, the chronologically first output emitted is
78- /// passed forward. We keep track of the other nodes so that if that output is
79- /// removed, we know which asset to replace it with.
83+ /// possible for multiple transformers to output an asset with the same id. In
84+ /// that case, the chronologically first output emitted is passed forward. We
85+ /// keep track of the other nodes so that if that output is removed, we know
86+ /// which asset to replace it with.
8087 final _outputs = new Map <AssetId , Queue <AssetNode >>();
8188
8289 /// A stream that emits an event whenever this phase becomes dirty and needs
@@ -163,36 +170,11 @@ class Phase {
163170 });
164171 }
165172
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-
189173 /// Gets the asset node for an input [id] .
190174 ///
191175 /// If an input with that ID cannot be found, returns null.
192176 Future <AssetNode > getInput (AssetId id) {
193177 return newFuture (() {
194- // TODO(rnystrom): Need to handle passthrough where an asset from a
195- // previous phase can be found.
196178 if (id.package == cascade.package) return _inputs[id];
197179 return cascade.graph.getAssetNode (id);
198180 });
@@ -210,6 +192,11 @@ class Phase {
210192 // kick off a build, even if that build does nothing.
211193 _onDirtyController.add (null );
212194
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+
213200 // Once the input is available, hook up transformers for it. If it changes
214201 // while that's happening, try again.
215202 _adjustTransformersFutures[node.id] = node.tryUntilStable ((asset) {
@@ -219,13 +206,17 @@ class Phase {
219206 return _removeStaleTransforms (asset)
220207 .then ((_) => _addFreshTransforms (node, oldTransformers));
221208 }).then ((_) {
209+ _adjustPassThrough (node);
210+
222211 // Now all the transforms are set up correctly and the asset is available
223212 // for the time being. Set up handlers for when the asset changes in the
224213 // future.
225214 node.onStateChange.first.then ((state) {
226215 if (state.isRemoved) {
227216 _onDirtyController.add (null );
228217 _transforms.remove (node.id);
218+ var passThrough = _passThroughControllers.remove (node.id);
219+ if (passThrough != null ) passThrough.setRemoved ();
229220 } else {
230221 _adjustTransformers (node);
231222 }
@@ -237,8 +228,10 @@ class Phase {
237228
238229 // If the asset is removed, [tryUntilStable] will throw an
239230 // [AssetNotFoundException]. In that case, just remove all transforms for
240- // the node.
231+ // the node, and its pass-through .
241232 _transforms.remove (node.id);
233+ var passThrough = _passThroughControllers.remove (node.id);
234+ if (passThrough != null ) passThrough.setRemoved ();
242235 }).whenComplete (() {
243236 _adjustTransformersFutures.remove (node.id);
244237 });
@@ -291,6 +284,28 @@ class Phase {
291284 }));
292285 }
293286
287+ /// Adjust whether [node] is passed through the phase unmodified, based on
288+ /// whether it's consumed by other transforms in this phase.
289+ ///
290+ /// If [node] was already passed-through, this will update the passed-through
291+ /// value.
292+ void _adjustPassThrough (AssetNode node) {
293+ assert (node.state.isAvailable);
294+
295+ if (_transforms[node.id].isEmpty) {
296+ var controller = _passThroughControllers[node.id];
297+ if (controller != null ) {
298+ controller.setAvailable (node.asset);
299+ } else {
300+ _passThroughControllers[node.id] =
301+ new AssetNodeController .available (node.asset, node.transform);
302+ }
303+ } else {
304+ var controller = _passThroughControllers.remove (node.id);
305+ if (controller != null ) controller.setRemoved ();
306+ }
307+ }
308+
294309 /// Processes this phase.
295310 ///
296311 /// Returns a future that completes when processing is done. If there is
@@ -308,14 +323,24 @@ class Phase {
308323
309324 /// Applies all currently wired up and dirty transforms.
310325 Future _processTransforms () {
326+ if (_next == null ) return ;
327+
328+ var newPassThroughs = _passThroughControllers.values
329+ .map ((controller) => controller.node)
330+ .where ((output) {
331+ return ! _outputs.containsKey (output.id) ||
332+ ! _outputs[output.id].contains (output);
333+ }).toSet ();
334+
311335 // Convert this to a list so we can safely modify _transforms while
312336 // iterating over it.
313337 var dirtyTransforms =
314338 flatten (_transforms.values.map ((transforms) => transforms.toList ()))
315339 .where ((transform) => transform.isDirty).toList ();
316- if (dirtyTransforms.isEmpty) return null ;
317340
318- var collisions = new Set <AssetId >();
341+ if (dirtyTransforms.isEmpty && newPassThroughs.isEmpty) return null ;
342+
343+ var collisions = _passAssetsThrough (newPassThroughs);
319344 return Future .wait (dirtyTransforms.map ((transform) {
320345 return transform.apply ().then ((outputs) {
321346 for (var output in outputs) {
@@ -346,6 +371,30 @@ class Phase {
346371 });
347372 }
348373
374+ /// Pass all new assets that aren't consumed by transforms through to the next
375+ /// phase.
376+ ///
377+ /// Returns a set of asset ids that have collisions between new passed-through
378+ /// assets and pre-existing transform outputs.
379+ Set <AssetId > _passAssetsThrough (Set <AssetId > newPassThroughs) {
380+ var collisions = new Set <AssetId >();
381+ for (var output in newPassThroughs) {
382+ if (_outputs.containsKey (output.id)) {
383+ // There shouldn't be another pass-through asset with the same id.
384+ assert (! _outputs[output.id].any ((asset) => asset.transform == null ));
385+
386+ _outputs[output.id].add (output);
387+ collisions.add (output.id);
388+ } else {
389+ _outputs[output.id] = new Queue <AssetNode >.from ([output]);
390+ _next.addInput (output);
391+ }
392+
393+ _handleOutputRemoval (output);
394+ }
395+ return collisions;
396+ }
397+
349398 /// Properly resolve collisions when [output] is removed.
350399 void _handleOutputRemoval (AssetNode output) {
351400 output.whenRemoved.then ((_) {
0 commit comments