Skip to content

Commit 41806f1

Browse files
committed
Fix build_outputs.json for rebuilds, and speed up rebuilds
1 parent c37e8eb commit 41806f1

File tree

6 files changed

+75
-22
lines changed

6 files changed

+75
-22
lines changed

lib/src/asset_graph/graph.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ class AssetGraph {
3636
/// Removes [node] from the graph.
3737
AssetNode remove(AssetId id) => _nodesById.remove(id);
3838

39+
/// Gets all nodes in the graph.
40+
Iterable<AssetNode> get allNodes => _nodesById.values;
41+
3942
@override
4043
toString() => _nodesById.values.toList().toString();
4144
}

lib/src/asset_graph/node.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,11 @@ class GeneratedAssetNode extends AssetNode {
3434
/// Whether or not this asset needs to be updated.
3535
bool needsUpdate;
3636

37+
/// Whether the asset was actually output.
38+
bool wasOutput;
39+
3740
GeneratedAssetNode(this.builder, this.primaryInput, this.generatingPhaseGroup,
38-
this.needsUpdate, AssetId id)
41+
this.needsUpdate, this.wasOutput, AssetId id)
3942
: super(id);
4043

4144
@override

lib/src/generate/build_impl.dart

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ class BuildImpl {
3434
bool _buildRunning = false;
3535
final _logger = new Logger('Build');
3636

37-
BuildImpl(this._assetGraph, this._reader, this._writer,
38-
this._packageGraph, this._phaseGroups);
37+
BuildImpl(this._assetGraph, this._reader, this._writer, this._packageGraph,
38+
this._phaseGroups);
3939

4040
/// Runs a build
4141
///
@@ -61,11 +61,13 @@ class BuildImpl {
6161
_logger.info('Running build phases');
6262
var result = await _runPhases();
6363

64-
// Write out the new build_outputs file.
64+
/// Write out the new build_outputs file.
65+
var allOuputs = _assetGraph.allNodes
66+
.where((node) => node is GeneratedAssetNode && node.wasOutput);
6567
var buildOutputsAsset = new Asset(
6668
_buildOutputsId,
6769
JSON.encode(
68-
result.outputs.map((output) => output.id.serialize()).toList()));
70+
allOuputs.map((output) => output.id.serialize()).toList()));
6971
await _writer.writeAsString(buildOutputsAsset);
7072

7173
return result;
@@ -280,7 +282,7 @@ class BuildImpl {
280282
_assetGraph.addIfAbsent(
281283
output,
282284
() => new GeneratedAssetNode(
283-
builder, input, phaseGroupNum, true, output));
285+
builder, input, phaseGroupNum, true, false, output));
284286
}
285287

286288
/// Skip the build step if none of the outputs need updating.
@@ -291,7 +293,7 @@ class BuildImpl {
291293
/// any files which were output last time, so they can be used by
292294
/// subsequent phases.
293295
for (var output in expectedOutputs) {
294-
if (await _reader.hasInput(output)) {
296+
if ((_assetGraph.get(output) as GeneratedAssetNode).wasOutput) {
295297
groupOutputs.add(output);
296298
}
297299
}
@@ -304,22 +306,27 @@ class BuildImpl {
304306
await builder.build(buildStep);
305307
await buildStep.complete();
306308

307-
/// Mark all outputs as no longer needing an update.
309+
/// Mark all outputs as no longer needing an update, and mark `wasOutput`
310+
/// as `false` for now (this will get reset to true later one).
308311
for (var output in expectedOutputs) {
309-
(_assetGraph.get(output) as GeneratedAssetNode).needsUpdate = false;
312+
(_assetGraph.get(output) as GeneratedAssetNode)
313+
..needsUpdate = false
314+
..wasOutput = false;
310315
}
311316

312317
/// Update the asset graph based on the dependencies discovered.
313318
for (var dependency in buildStep.dependencies) {
314319
var dependencyNode = _assetGraph.addIfAbsent(
315320
dependency, () => new AssetNode(dependency));
316321

317-
/// We care about all [expectedOutputs], not just real outputs.
322+
/// We care about all [expectedOutputs], not just real outputs. Updates
323+
/// to dependencies may cause a file to be output which wasn't before.
318324
dependencyNode.outputs.addAll(expectedOutputs);
319325
}
320326

321327
/// Yield the outputs.
322328
for (var output in buildStep.outputs) {
329+
(_assetGraph.get(output.id) as GeneratedAssetNode).wasOutput = true;
323330
groupOutputs.add(output.id);
324331
yield output;
325332
}

lib/src/generate/watch_impl.dart

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ class WatchImpl {
111111
_runningWatch = true;
112112
_resultStreamController = new StreamController<BuildResult>();
113113
_nextBuildScheduled = false;
114-
var updatedInputs = new Set<AssetId>();
114+
var updatedInputs = new Map<AssetId, ChangeType>();
115115

116116
doBuild([bool force = false]) {
117117
// Don't schedule more builds if we are turning down.
@@ -128,24 +128,32 @@ class WatchImpl {
128128

129129
/// Remove any updates that were generated outputs or otherwise not
130130
/// interesting.
131-
updatedInputs.removeWhere(_shouldSkipInput);
131+
var updatesToRemove = updatedInputs.keys.where(_shouldSkipInput).toList();
132+
updatesToRemove.forEach(updatedInputs.remove);
132133
if (updatedInputs.isEmpty && !force) {
133134
return;
134135
}
135136

136137
_logger.info('Preparing for next build');
137138
_logger.info('Clearing cache for invalidated assets');
138-
void clearNodeAndDeps(AssetId id) {
139+
void clearNodeAndDeps(AssetId id, ChangeType rootChangeType) {
139140
var node = _assetGraph.get(id);
140141
if (node == null) return;
141-
if (node is GeneratedAssetNode) node.needsUpdate = true;
142+
if (node is GeneratedAssetNode) {
143+
node.needsUpdate = true;
144+
}
142145
_assetCache.remove(id);
143146
for (var output in node.outputs) {
144-
clearNodeAndDeps(output);
147+
clearNodeAndDeps(output, rootChangeType);
148+
}
149+
150+
/// For deletes, prune the graph.
151+
if (rootChangeType == ChangeType.REMOVE) {
152+
_assetGraph.remove(id);
145153
}
146154
}
147-
for (var input in updatedInputs) {
148-
clearNodeAndDeps(input);
155+
for (var input in updatedInputs.keys) {
156+
clearNodeAndDeps(input, updatedInputs[input]);
149157
}
150158
updatedInputs.clear();
151159

@@ -181,7 +189,7 @@ class WatchImpl {
181189
_allListeners.add(watcher.events.listen((WatchEvent e) {
182190
_logger.fine('Got WatchEvent for path ${e.path}');
183191
var id = new AssetId(package.name, path.normalize(e.path));
184-
updatedInputs.add(id);
192+
updatedInputs[id] = e.type;
185193
scheduleBuild();
186194
}));
187195
}

test/asset_graph/graph_test.dart

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,19 @@ main() {
3434
return node;
3535
}
3636

37-
test('add, contains, get', () {
37+
test('add, contains, get, allNodes', () {
38+
var expectedNodes = [];
3839
for (int i = 0; i < 5; i++) {
39-
testAddNode();
40+
expectedNodes.add(testAddNode());
4041
}
42+
expect(graph.allNodes, unorderedEquals(expectedNodes));
4143
});
4244

4345
test('addIfAbsent', () {
4446
var node = makeAssetNode();
4547
expect(graph.addIfAbsent(node.id, () => node), same(node));
4648
expect(graph.contains(node.id), isTrue);
47-
49+
4850
var otherNode = new AssetNode(node.id);
4951
expect(graph.addIfAbsent(otherNode.id, () => otherNode), same(node));
5052
expect(graph.contains(otherNode.id), isTrue);

test/generate/watch_test.dart

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,35 @@ main() {
9898
// Previous outputs should still exist.
9999
expect(writer.assets[makeAssetId('a|web/b.txt.copy')], 'b');
100100
});
101+
102+
test('rebuilds properly update build_outputs.json', () async {
103+
var phases = [
104+
[
105+
new Phase([new CopyBuilder()], [new InputSet('a')]),
106+
]
107+
];
108+
var writer = new InMemoryAssetWriter();
109+
var results = [];
110+
startWatch(phases, {'a|web/a.txt': 'a', 'a|web/b.txt': 'b'}, writer)
111+
.listen(results.add);
112+
113+
var result = await nextResult(results);
114+
checkOutputs({'a|web/a.txt.copy': 'a', 'a|web/b.txt.copy': 'b',},
115+
result, writer.assets);
116+
117+
await writer.writeAsString(makeAsset('a|web/c.txt', 'c'));
118+
FakeWatcher
119+
.notifyWatchers(new WatchEvent(ChangeType.ADD, 'a/web/c.txt'));
120+
await writer.delete(makeAssetId('a|web/a.txt'));
121+
FakeWatcher
122+
.notifyWatchers(new WatchEvent(ChangeType.REMOVE, 'a/web/a.txt'));
123+
124+
result = await nextResult(results);
125+
checkOutputs({'a|web/c.txt.copy': 'c'}, result, writer.assets);
126+
127+
expect(writer.assets[makeAssetId('a|.build/build_outputs.json')],
128+
'[["a","web/b.txt.copy"],["a","web/c.txt.copy"]]');
129+
});
101130
});
102131

103132
group('multiple phases', () {
@@ -260,7 +289,8 @@ main() {
260289
.listen(results.add);
261290

262291
var result = await nextResult(results);
263-
checkOutputs({'a|web/a.txt.copy': 'a', 'a|web/a.txt.copy.copy': 'b'}, result, writer.assets);
292+
checkOutputs({'a|web/a.txt.copy': 'a', 'a|web/a.txt.copy.copy': 'b'},
293+
result, writer.assets);
264294

265295
await writer.writeAsString(makeAsset('a|web/b.txt', 'c'));
266296
FakeWatcher

0 commit comments

Comments
 (0)