Skip to content

Commit ce57b84

Browse files
committed
Version 0.6.21.0
svn merge -r 26492:26574 https://dart.googlecode.com/svn/branches/bleeding_edge trunk git-svn-id: http://dart.googlecode.com/svn/trunk@26575 260f80e4-7a28-3924-810f-c04153c831b5
2 parents 1279f7b + 6839e72 commit ce57b84

File tree

115 files changed

+3319
-858
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

115 files changed

+3319
-858
lines changed

pkg/barback/lib/src/asset_cascade.dart

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -186,12 +186,11 @@ class AssetCascade {
186186
_addPhase(_phases.last.addPhase(transformers[i]));
187187
}
188188

189-
if (transformers.length < _phases.length) {
190-
for (var i = transformers.length; i < _phases.length; i++) {
191-
// TODO(nweiz): actually remove phases rather than emptying them of
192-
// transformers.
193-
_phases[i].updateTransformers([]);
194-
}
189+
if (transformers.length == 0) {
190+
_phases.last.updateTransformers([]);
191+
} else if (transformers.length < _phases.length) {
192+
_phases[transformers.length - 1].removeFollowing();
193+
_phases.removeRange(transformers.length, _phases.length);
195194
}
196195
}
197196

pkg/barback/lib/src/asset_forwarder.dart

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ class AssetForwarder {
2525
AssetNode get node => _controller.node;
2626

2727
AssetForwarder(AssetNode node)
28-
: _controller = new AssetNodeController(node.id, node.transform) {
28+
: _controller = new AssetNodeController.from(node) {
29+
if (node.state.isRemoved) return;
30+
2931
_subscription = node.onStateChange.listen((state) {
3032
if (state.isAvailable) {
3133
_controller.setAvailable(node.asset);
@@ -36,12 +38,6 @@ class AssetForwarder {
3638
close();
3739
}
3840
});
39-
40-
if (node.state.isAvailable) {
41-
_controller.setAvailable(node.asset);
42-
} else if (node.state.isRemoved) {
43-
close();
44-
}
4541
}
4642

4743
/// Closes the forwarder and marks [node] as removed.

pkg/barback/lib/src/asset_id.dart

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@ library barback.asset_id;
66

77
import 'package:path/path.dart' as pathos;
88

9-
/// AssetIDs always use POSIX style paths regardless of the host platform.
10-
final _posix = new pathos.Builder(style: pathos.Style.posix);
11-
129
/// Identifies an asset within a package.
1310
class AssetId implements Comparable<AssetId> {
1411
/// The name of the package containing this asset.
@@ -29,10 +26,18 @@ class AssetId implements Comparable<AssetId> {
2926
String get extension => pathos.extension(path);
3027

3128
/// Creates a new AssetId at [path] within [package].
29+
///
30+
/// The [path] will be normalized: any backslashes will be replaced with
31+
/// forward slashes (regardless of host OS) and "." and ".." will be removed
32+
/// where possible.
3233
AssetId(this.package, String path)
33-
: path = _posix.normalize(path);
34+
: path = _normalizePath(path);
3435

3536
/// Parses an [AssetId] string of the form "package|path/to/asset.txt".
37+
///
38+
/// The [path] will be normalized: any backslashes will be replaced with
39+
/// forward slashes (regardless of host OS) and "." and ".." will be removed
40+
/// where possible.
3641
factory AssetId.parse(String description) {
3742
var parts = description.split("|");
3843
if (parts.length != 2) {
@@ -92,3 +97,15 @@ class AssetId implements Comparable<AssetId> {
9297
/// and passed to [deserialize].
9398
serialize() => [package, path];
9499
}
100+
101+
String _normalizePath(String path) {
102+
if (pathos.isAbsolute(path)) {
103+
throw new ArgumentError('Asset paths must be relative, but got "$path".');
104+
}
105+
106+
// Normalize path separators so that they are always "/" in the AssetID.
107+
path = path.replaceAll(r"\", "/");
108+
109+
// Collapse "." and "..".
110+
return pathos.posix.normalize(path);
111+
}

pkg/barback/lib/src/asset_node.dart

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,11 @@ class AssetNode {
2323

2424
/// The transform that created this asset node.
2525
///
26-
/// This is `null` for source assets.
27-
final TransformNode transform;
26+
/// This is `null` for source assets. It can change if the upstream transform
27+
/// that created this asset changes; this change will *not* cause an
28+
/// [onStateChange] event.
29+
TransformNode get transform => _transform;
30+
TransformNode _transform;
2831

2932
/// The current state of the asset node.
3033
AssetState get state => _state;
@@ -110,10 +113,10 @@ class AssetNode {
110113
return onStateChange.firstWhere(test);
111114
}
112115

113-
AssetNode._(this.id, this.transform)
116+
AssetNode._(this.id, this._transform)
114117
: _state = AssetState.DIRTY;
115118

116-
AssetNode._available(Asset asset, this.transform)
119+
AssetNode._available(Asset asset, this._transform)
117120
: id = asset.id,
118121
_asset = asset,
119122
_state = AssetState.AVAILABLE;
@@ -134,6 +137,17 @@ class AssetNodeController {
134137
AssetNodeController.available(Asset asset, [TransformNode transform])
135138
: node = new AssetNode._available(asset, transform);
136139

140+
/// Creates a controller for a node whose initial state matches the current
141+
/// state of [node].
142+
AssetNodeController.from(AssetNode node)
143+
: node = new AssetNode._(node.id, node.transform) {
144+
if (node.state.isAvailable) {
145+
setAvailable(node.asset);
146+
} else if (node.state.isRemoved) {
147+
setRemoved();
148+
}
149+
}
150+
137151
/// Marks the node as [AssetState.DIRTY].
138152
void setDirty() {
139153
assert(node._state != AssetState.REMOVED);
@@ -165,6 +179,14 @@ class AssetNodeController {
165179
node._asset = asset;
166180
node._stateChangeController.add(AssetState.AVAILABLE);
167181
}
182+
183+
/// Sets the node's [AssetNode.transform] property.
184+
///
185+
/// This is used when resolving collisions, where a node will stick around but
186+
/// a different transform will have created it.
187+
void setTransform(TransformNode transform) {
188+
node._transform = transform;
189+
}
168190
}
169191

170192
// TODO(nweiz): add an error state.

pkg/barback/lib/src/phase.dart

Lines changed: 54 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import 'asset_node.dart';
1313
import 'asset_set.dart';
1414
import 'errors.dart';
1515
import 'phase_input.dart';
16+
import 'phase_output.dart';
1617
import 'stream_pool.dart';
1718
import 'transformer.dart';
1819
import 'utils.dart';
@@ -44,15 +45,8 @@ class Phase {
4445
/// phases, they will be the outputs from the previous phase.
4546
final _inputs = new Map<AssetId, PhaseInput>();
4647

47-
/// A map of output ids to the asset node outputs for those ids and the
48-
/// transforms that produced those asset nodes.
49-
///
50-
/// Usually there's only one node for a given output id. However, it's
51-
/// possible for multiple transformers to output an asset with the same id. In
52-
/// that case, the chronologically first output emitted is passed forward. We
53-
/// keep track of the other nodes so that if that output is removed, we know
54-
/// which asset to replace it with.
55-
final _outputs = new Map<AssetId, Queue<AssetNode>>();
48+
/// The outputs for this phase.
49+
final _outputs = new Map<AssetId, PhaseOutput>();
5650

5751
/// A stream that emits an event whenever this phase becomes dirty and needs
5852
/// to be run.
@@ -77,7 +71,7 @@ class Phase {
7771
/// Returns all currently-available output assets for this phase.
7872
AssetSet get availableOutputs {
7973
return new AssetSet.from(_outputs.values
80-
.map((queue) => queue.first)
74+
.map((output) => output.output)
8175
.where((node) => node.state.isAvailable)
8276
.map((node) => node.asset));
8377
}
@@ -125,7 +119,7 @@ class Phase {
125119
return newFuture(() {
126120
if (id.package != cascade.package) return cascade.graph.getAssetNode(id);
127121
if (!_outputs.containsKey(id)) return null;
128-
return _outputs[id].first;
122+
return _outputs[id].output;
129123
});
130124
}
131125

@@ -145,89 +139,71 @@ class Phase {
145139
Phase addPhase(Iterable<Transformer> transformers) {
146140
assert(_next == null);
147141
_next = new Phase(cascade, transformers);
148-
for (var outputs in _outputs.values) {
149-
_next.addInput(outputs.first);
142+
for (var output in _outputs.values.toList()) {
143+
// Remove [output]'s listeners because now they should get the asset from
144+
// [_next], rather than this phase. Any transforms consuming [output] will
145+
// be re-run and will consume the output from the new final phase.
146+
output.removeListeners();
147+
148+
// Removing [output]'s listeners will cause it to be removed from
149+
// [_outputs], so we have to put it back.
150+
_outputs[output.output.id] = output;
151+
_next.addInput(output.output);
150152
}
151153
return _next;
152154
}
153155

156+
/// Mark this phase as removed.
157+
///
158+
/// This will remove all the phase's outputs and all following phases.
159+
void remove() {
160+
removeFollowing();
161+
for (var input in _inputs.values.toList()) {
162+
input.remove();
163+
}
164+
_onDirtyPool.close();
165+
}
166+
167+
/// Remove all phases after this one.
168+
Phase removeFollowing() {
169+
if (_next == null) return;
170+
_next.remove();
171+
_next = null;
172+
}
173+
154174
/// Processes this phase.
155175
///
156176
/// Returns a future that completes when processing is done. If there is
157177
/// nothing to process, returns `null`.
158178
Future process() {
159179
if (!_inputs.values.any((input) => input.isDirty)) return null;
160180

181+
var outputIds = new Set<AssetId>();
161182
return Future.wait(_inputs.values.map((input) {
162183
if (!input.isDirty) return new Future.value(new Set());
163184
return input.process().then((outputs) {
164-
return outputs.where(_addOutput).map((output) => output.id).toSet();
185+
for (var asset in outputs) {
186+
outputIds.add(asset.id);
187+
if (_outputs.containsKey(asset.id)) {
188+
_outputs[asset.id].add(asset);
189+
} else {
190+
_outputs[asset.id] = new PhaseOutput(this, asset);
191+
_outputs[asset.id].output.whenRemoved
192+
.then((_) => _outputs.remove(asset.id));
193+
if (_next != null) _next.addInput(_outputs[asset.id].output);
194+
}
195+
}
165196
});
166-
})).then((collisionsList) {
197+
})).then((_) {
167198
// Report collisions in a deterministic order.
168-
var collisions = unionAll(collisionsList).toList();
169-
collisions.sort((a, b) => a.compareTo(b));
170-
for (var collision in collisions) {
171-
// Ensure that there's still a collision. It's possible it was resolved
172-
// while another transform was running.
173-
if (_outputs[collision].length <= 1) continue;
174-
cascade.reportError(new AssetCollisionException(
175-
_outputs[collision].where((asset) => asset.transform != null)
176-
.map((asset) => asset.transform.info),
177-
collision));
178-
}
179-
});
180-
}
181-
182-
/// Add [output] as an output of this phase, forwarding it to the next phase
183-
/// if necessary.
184-
///
185-
/// Returns whether or not [output] collides with another pre-existing output.
186-
bool _addOutput(AssetNode output) {
187-
_handleOutputRemoval(output);
188-
189-
if (_outputs.containsKey(output.id)) {
190-
_outputs[output.id].add(output);
191-
return true;
192-
}
193-
194-
_outputs[output.id] = new Queue<AssetNode>.from([output]);
195-
if (_next != null) _next.addInput(output);
196-
return false;
197-
}
198-
199-
/// Properly resolve collisions when [output] is removed.
200-
void _handleOutputRemoval(AssetNode output) {
201-
output.whenRemoved.then((_) {
202-
var assets = _outputs[output.id];
203-
if (assets.length == 1) {
204-
assert(assets.single == output);
205-
_outputs.remove(output.id);
206-
return;
207-
}
208-
209-
// If there was more than one asset, we're resolving a collision --
210-
// possibly partially.
211-
var wasFirst = assets.first == output;
212-
assets.remove(output);
213-
214-
// If this was the first asset, we need to pass the next asset
215-
// (chronologically) to the next phase. Pump the event queue first to give
216-
// [_next] a chance to handle the removal of its input before getting a
217-
// new input.
218-
if (wasFirst && _next != null) {
219-
newFuture(() => _next.addInput(assets.first));
220-
}
221-
222-
// If there's still a collision, report it. This lets the user know
223-
// if they've successfully resolved the collision or not.
224-
if (assets.length > 1) {
225-
// Pump the event queue to ensure that the removal of the input triggers
226-
// a new build to which we can attach the error.
227-
newFuture(() => cascade.reportError(new AssetCollisionException(
228-
assets.where((asset) => asset.transform != null)
229-
.map((asset) => asset.transform.info),
230-
output.id)));
199+
outputIds = outputIds.toList();
200+
outputIds.sort((a, b) => a.compareTo(b));
201+
for (var id in outputIds) {
202+
// It's possible the output was removed before other transforms in this
203+
// phase finished.
204+
if (!_outputs.containsKey(id)) continue;
205+
var exception = _outputs[id].collisionException;
206+
if (exception != null) cascade.reportError(exception);
231207
}
232208
});
233209
}

0 commit comments

Comments
 (0)