Skip to content

Commit

Permalink
Version 0.6.21.0
Browse files Browse the repository at this point in the history
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
  • Loading branch information
ricowind committed May 27, 2015
2 parents 1279f7b + 6839e72 commit ce57b84
Show file tree
Hide file tree
Showing 115 changed files with 3,319 additions and 858 deletions.
11 changes: 5 additions & 6 deletions pkg/barback/lib/src/asset_cascade.dart
Original file line number Diff line number Diff line change
Expand Up @@ -186,12 +186,11 @@ class AssetCascade {
_addPhase(_phases.last.addPhase(transformers[i]));
}

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

Expand Down
10 changes: 3 additions & 7 deletions pkg/barback/lib/src/asset_forwarder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ class AssetForwarder {
AssetNode get node => _controller.node;

AssetForwarder(AssetNode node)
: _controller = new AssetNodeController(node.id, node.transform) {
: _controller = new AssetNodeController.from(node) {
if (node.state.isRemoved) return;

_subscription = node.onStateChange.listen((state) {
if (state.isAvailable) {
_controller.setAvailable(node.asset);
Expand All @@ -36,12 +38,6 @@ class AssetForwarder {
close();
}
});

if (node.state.isAvailable) {
_controller.setAvailable(node.asset);
} else if (node.state.isRemoved) {
close();
}
}

/// Closes the forwarder and marks [node] as removed.
Expand Down
25 changes: 21 additions & 4 deletions pkg/barback/lib/src/asset_id.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ library barback.asset_id;

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

/// AssetIDs always use POSIX style paths regardless of the host platform.
final _posix = new pathos.Builder(style: pathos.Style.posix);

/// Identifies an asset within a package.
class AssetId implements Comparable<AssetId> {
/// The name of the package containing this asset.
Expand All @@ -29,10 +26,18 @@ class AssetId implements Comparable<AssetId> {
String get extension => pathos.extension(path);

/// Creates a new AssetId at [path] within [package].
///
/// The [path] will be normalized: any backslashes will be replaced with
/// forward slashes (regardless of host OS) and "." and ".." will be removed
/// where possible.
AssetId(this.package, String path)
: path = _posix.normalize(path);
: path = _normalizePath(path);

/// Parses an [AssetId] string of the form "package|path/to/asset.txt".
///
/// The [path] will be normalized: any backslashes will be replaced with
/// forward slashes (regardless of host OS) and "." and ".." will be removed
/// where possible.
factory AssetId.parse(String description) {
var parts = description.split("|");
if (parts.length != 2) {
Expand Down Expand Up @@ -92,3 +97,15 @@ class AssetId implements Comparable<AssetId> {
/// and passed to [deserialize].
serialize() => [package, path];
}

String _normalizePath(String path) {
if (pathos.isAbsolute(path)) {
throw new ArgumentError('Asset paths must be relative, but got "$path".');
}

// Normalize path separators so that they are always "/" in the AssetID.
path = path.replaceAll(r"\", "/");

// Collapse "." and "..".
return pathos.posix.normalize(path);
}
30 changes: 26 additions & 4 deletions pkg/barback/lib/src/asset_node.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@ class AssetNode {

/// The transform that created this asset node.
///
/// This is `null` for source assets.
final TransformNode transform;
/// This is `null` for source assets. It can change if the upstream transform
/// that created this asset changes; this change will *not* cause an
/// [onStateChange] event.
TransformNode get transform => _transform;
TransformNode _transform;

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

AssetNode._(this.id, this.transform)
AssetNode._(this.id, this._transform)
: _state = AssetState.DIRTY;

AssetNode._available(Asset asset, this.transform)
AssetNode._available(Asset asset, this._transform)
: id = asset.id,
_asset = asset,
_state = AssetState.AVAILABLE;
Expand All @@ -134,6 +137,17 @@ class AssetNodeController {
AssetNodeController.available(Asset asset, [TransformNode transform])
: node = new AssetNode._available(asset, transform);

/// Creates a controller for a node whose initial state matches the current
/// state of [node].
AssetNodeController.from(AssetNode node)
: node = new AssetNode._(node.id, node.transform) {
if (node.state.isAvailable) {
setAvailable(node.asset);
} else if (node.state.isRemoved) {
setRemoved();
}
}

/// Marks the node as [AssetState.DIRTY].
void setDirty() {
assert(node._state != AssetState.REMOVED);
Expand Down Expand Up @@ -165,6 +179,14 @@ class AssetNodeController {
node._asset = asset;
node._stateChangeController.add(AssetState.AVAILABLE);
}

/// Sets the node's [AssetNode.transform] property.
///
/// This is used when resolving collisions, where a node will stick around but
/// a different transform will have created it.
void setTransform(TransformNode transform) {
node._transform = transform;
}
}

// TODO(nweiz): add an error state.
Expand Down
132 changes: 54 additions & 78 deletions pkg/barback/lib/src/phase.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import 'asset_node.dart';
import 'asset_set.dart';
import 'errors.dart';
import 'phase_input.dart';
import 'phase_output.dart';
import 'stream_pool.dart';
import 'transformer.dart';
import 'utils.dart';
Expand Down Expand Up @@ -44,15 +45,8 @@ class Phase {
/// phases, they will be the outputs from the previous phase.
final _inputs = new Map<AssetId, PhaseInput>();

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

/// A stream that emits an event whenever this phase becomes dirty and needs
/// to be run.
Expand All @@ -77,7 +71,7 @@ class Phase {
/// Returns all currently-available output assets for this phase.
AssetSet get availableOutputs {
return new AssetSet.from(_outputs.values
.map((queue) => queue.first)
.map((output) => output.output)
.where((node) => node.state.isAvailable)
.map((node) => node.asset));
}
Expand Down Expand Up @@ -125,7 +119,7 @@ class Phase {
return newFuture(() {
if (id.package != cascade.package) return cascade.graph.getAssetNode(id);
if (!_outputs.containsKey(id)) return null;
return _outputs[id].first;
return _outputs[id].output;
});
}

Expand All @@ -145,89 +139,71 @@ class Phase {
Phase addPhase(Iterable<Transformer> transformers) {
assert(_next == null);
_next = new Phase(cascade, transformers);
for (var outputs in _outputs.values) {
_next.addInput(outputs.first);
for (var output in _outputs.values.toList()) {
// Remove [output]'s listeners because now they should get the asset from
// [_next], rather than this phase. Any transforms consuming [output] will
// be re-run and will consume the output from the new final phase.
output.removeListeners();

// Removing [output]'s listeners will cause it to be removed from
// [_outputs], so we have to put it back.
_outputs[output.output.id] = output;
_next.addInput(output.output);
}
return _next;
}

/// Mark this phase as removed.
///
/// This will remove all the phase's outputs and all following phases.
void remove() {
removeFollowing();
for (var input in _inputs.values.toList()) {
input.remove();
}
_onDirtyPool.close();
}

/// Remove all phases after this one.
Phase removeFollowing() {
if (_next == null) return;
_next.remove();
_next = null;
}

/// Processes this phase.
///
/// Returns a future that completes when processing is done. If there is
/// nothing to process, returns `null`.
Future process() {
if (!_inputs.values.any((input) => input.isDirty)) return null;

var outputIds = new Set<AssetId>();
return Future.wait(_inputs.values.map((input) {
if (!input.isDirty) return new Future.value(new Set());
return input.process().then((outputs) {
return outputs.where(_addOutput).map((output) => output.id).toSet();
for (var asset in outputs) {
outputIds.add(asset.id);
if (_outputs.containsKey(asset.id)) {
_outputs[asset.id].add(asset);
} else {
_outputs[asset.id] = new PhaseOutput(this, asset);
_outputs[asset.id].output.whenRemoved
.then((_) => _outputs.remove(asset.id));
if (_next != null) _next.addInput(_outputs[asset.id].output);
}
}
});
})).then((collisionsList) {
})).then((_) {
// Report collisions in a deterministic order.
var collisions = unionAll(collisionsList).toList();
collisions.sort((a, b) => a.compareTo(b));
for (var collision in collisions) {
// Ensure that there's still a collision. It's possible it was resolved
// while another transform was running.
if (_outputs[collision].length <= 1) continue;
cascade.reportError(new AssetCollisionException(
_outputs[collision].where((asset) => asset.transform != null)
.map((asset) => asset.transform.info),
collision));
}
});
}

/// Add [output] as an output of this phase, forwarding it to the next phase
/// if necessary.
///
/// Returns whether or not [output] collides with another pre-existing output.
bool _addOutput(AssetNode output) {
_handleOutputRemoval(output);

if (_outputs.containsKey(output.id)) {
_outputs[output.id].add(output);
return true;
}

_outputs[output.id] = new Queue<AssetNode>.from([output]);
if (_next != null) _next.addInput(output);
return false;
}

/// Properly resolve collisions when [output] is removed.
void _handleOutputRemoval(AssetNode output) {
output.whenRemoved.then((_) {
var assets = _outputs[output.id];
if (assets.length == 1) {
assert(assets.single == output);
_outputs.remove(output.id);
return;
}

// If there was more than one asset, we're resolving a collision --
// possibly partially.
var wasFirst = assets.first == output;
assets.remove(output);

// If this was the first asset, we need to pass the next asset
// (chronologically) to the next phase. Pump the event queue first to give
// [_next] a chance to handle the removal of its input before getting a
// new input.
if (wasFirst && _next != null) {
newFuture(() => _next.addInput(assets.first));
}

// If there's still a collision, report it. This lets the user know
// if they've successfully resolved the collision or not.
if (assets.length > 1) {
// Pump the event queue to ensure that the removal of the input triggers
// a new build to which we can attach the error.
newFuture(() => cascade.reportError(new AssetCollisionException(
assets.where((asset) => asset.transform != null)
.map((asset) => asset.transform.info),
output.id)));
outputIds = outputIds.toList();
outputIds.sort((a, b) => a.compareTo(b));
for (var id in outputIds) {
// It's possible the output was removed before other transforms in this
// phase finished.
if (!_outputs.containsKey(id)) continue;
var exception = _outputs[id].collisionException;
if (exception != null) cascade.reportError(exception);
}
});
}
Expand Down
Loading

0 comments on commit ce57b84

Please sign in to comment.