Skip to content

Commit da8ac96

Browse files
committed
Lazily compute outputs.
1 parent 3e6e1ac commit da8ac96

File tree

13 files changed

+112
-184
lines changed

13 files changed

+112
-184
lines changed

build_runner/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
- Add `NodeType` to `AssetNode`, remove subtypes. Make mutations explicit.
1414
- Use `built_value` for `AssetNode` and related types.
1515
- Add details of what changed and what is built to `--verbose` logging.
16+
- Compute outputs as needed instead of storing them in the asset graph.
1617

1718
## 2.4.15
1819

build_runner/bin/graph_inspector.dart

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ class InspectNodeCommand extends Command<bool> {
118118

119119
@override
120120
bool run() {
121+
var computedOutputs = assetGraph.computeOutputs();
121122
var argResults = this.argResults!;
122123
var stringUris = argResults.rest;
123124
if (stringUris.isEmpty) {
@@ -157,12 +158,16 @@ class InspectNodeCommand extends Command<bool> {
157158
node.primaryOutputs.forEach(printAsset);
158159

159160
description.writeln(' secondary outputs:');
160-
node.outputs.difference(node.primaryOutputs).forEach(printAsset);
161+
(computedOutputs[node.id] ?? const <AssetId>{})
162+
.difference(node.primaryOutputs.asSet())
163+
.forEach(printAsset);
161164

162165
if (node.type == NodeType.generated || node.type == NodeType.glob) {
163166
description.writeln(' inputs:');
164167
assetGraph.allNodes
165-
.where((n) => n.outputs.contains(node.id))
168+
.where(
169+
(n) => (computedOutputs[n.id] ?? <AssetId>{}).contains(node.id),
170+
)
166171
.map((n) => n.id)
167172
.forEach(printAsset);
168173
}

build_runner/lib/src/server/asset_graph_handler.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,8 @@ class AssetGraphHandler {
115115
{'id': '${node.id}', 'label': '${node.id}'},
116116
];
117117
var edges = <Map<String, String>>[];
118-
for (final output in node.outputs) {
118+
var computedOutputs = _assetGraph.computeOutputs();
119+
for (final output in (computedOutputs[node.id] ?? <AssetId>{})) {
119120
if (filterGlob != null && !filterGlob.matches(output.toString())) {
120121
continue;
121122
}

build_runner/test/generate/watch_test.dart

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -382,9 +382,6 @@ void main() {
382382
inputs: [makeAssetId('a|web/b.txt')],
383383
isHidden: false,
384384
);
385-
builderOptionsNode = builderOptionsNode.rebuild(
386-
(b) => b..outputs.add(bCopyNode.id),
387-
);
388385
expectedGraph
389386
..add(bCopyNode)
390387
..add(
@@ -407,9 +404,6 @@ void main() {
407404
inputs: [makeAssetId('a|web/c.txt')],
408405
isHidden: false,
409406
);
410-
builderOptionsNode = builderOptionsNode.rebuild(
411-
(b) => b..outputs.add(cCopyNode.id),
412-
);
413407
expectedGraph
414408
..add(cCopyNode)
415409
..add(

build_runner_core/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
- Use `LibraryCycleGraphLoader` to load transitive deps for analysis.
3030
- Track post process builder outputs separately from the main graph Instead of
3131
in `postProcessAnchor` nodes.
32+
- Compute outputs as needed instead of storing them in the asset graph.
3233

3334
## 8.0.0
3435

build_runner_core/lib/src/asset_graph/graph.dart

Lines changed: 58 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ class AssetGraph implements GeneratedAssetHider {
4949

5050
final BuiltMap<String, LanguageVersion?> packageLanguageVersions;
5151

52+
/// The result of [computeOutputs] for reuse, or `null` if outputs have not
53+
/// been computed.
54+
Map<AssetId, Set<AssetId>>? _outputs;
55+
5256
/// All post process build steps outputs, indexed by package then
5357
/// [PostProcessBuildStepId].
5458
///
@@ -129,6 +133,11 @@ class AssetGraph implements GeneratedAssetHider {
129133
if (node == null) throw StateError('Missing node: $id');
130134
final updatedNode = node.rebuild(updates);
131135
_nodesByPackage[id.package]![id.path] = updatedNode;
136+
137+
if (node.inputs != updatedNode.inputs) {
138+
_outputs = null;
139+
}
140+
132141
return updatedNode;
133142
}
134143

@@ -145,6 +154,11 @@ class AssetGraph implements GeneratedAssetHider {
145154
if (node == null) return null;
146155
final updatedNode = node.rebuild(updates);
147156
_nodesByPackage[id.package]![id.path] = updatedNode;
157+
158+
if (node.inputs != updatedNode.inputs) {
159+
_outputs = null;
160+
}
161+
148162
return updatedNode;
149163
}
150164

@@ -162,7 +176,6 @@ class AssetGraph implements GeneratedAssetHider {
162176
// primary outputs. We only want to remove this node.
163177
_nodesByPackage[existing.id.package]!.remove(existing.id.path);
164178
node = node.rebuild((b) {
165-
b.outputs.addAll(existing.outputs);
166179
b.primaryOutputs.addAll(existing.primaryOutputs);
167180
});
168181
} else {
@@ -173,6 +186,10 @@ class AssetGraph implements GeneratedAssetHider {
173186
}
174187
}
175188
_nodesByPackage.putIfAbsent(node.id.package, () => {})[node.id.path] = node;
189+
if (node.inputs?.isNotEmpty ?? false) {
190+
_outputs = null;
191+
}
192+
176193
return node;
177194
}
178195

@@ -261,7 +278,8 @@ class AssetGraph implements GeneratedAssetHider {
261278
for (var output in node.primaryOutputs.toList()) {
262279
_removeRecursive(output, removedIds: removedIds);
263280
}
264-
for (var output in node.outputs) {
281+
final outputs = computeOutputs();
282+
for (var output in (outputs[node.id] ?? const <AssetId>{})) {
265283
updateNodeIfPresent(output, (nodeBuilder) {
266284
if (nodeBuilder.type == NodeType.generated) {
267285
nodeBuilder.generatedNodeState.inputs.remove(id);
@@ -277,23 +295,14 @@ class AssetGraph implements GeneratedAssetHider {
277295
for (var input in node.generatedNodeState!.inputs) {
278296
// We may have already removed this node entirely.
279297
updateNodeIfPresent(input, (nodeBuilder) {
280-
nodeBuilder
281-
..outputs.remove(id)
282-
..primaryOutputs.remove(id);
298+
nodeBuilder.primaryOutputs.remove(id);
283299
});
284300
}
285-
updateNode(node.generatedNodeConfiguration!.builderOptionsId, (
286-
nodeBuilder,
287-
) {
288-
nodeBuilder.outputs.remove(id);
289-
});
290301
} else if (node.type == NodeType.glob) {
291302
for (var input in node.globNodeState!.inputs) {
292303
// We may have already removed this node entirely.
293304
updateNodeIfPresent(input, (nodeBuilder) {
294-
nodeBuilder
295-
..outputs.remove(id)
296-
..primaryOutputs.remove(id);
305+
nodeBuilder.primaryOutputs.remove(id);
297306
});
298307
}
299308
}
@@ -311,6 +320,34 @@ class AssetGraph implements GeneratedAssetHider {
311320
return removedIds;
312321
}
313322

323+
/// Computes node outputs: the inverse of the graph described by the `inputs`
324+
/// fields on glob and generated nodes.
325+
///
326+
/// The result is cached until any node is updated with different `inputs` or
327+
/// [updateAndInvalidate] is called.
328+
Map<AssetId, Set<AssetId>> computeOutputs() {
329+
if (_outputs != null) return _outputs!;
330+
final result = <AssetId, Set<AssetId>>{};
331+
for (final node in allNodes) {
332+
if (node.type == NodeType.generated) {
333+
for (final input in node.generatedNodeState!.inputs) {
334+
result.putIfAbsent(input, () => {}).add(node.id);
335+
}
336+
result
337+
.putIfAbsent(
338+
node.generatedNodeConfiguration!.builderOptionsId,
339+
() => {},
340+
)
341+
.add(node.id);
342+
} else if (node.type == NodeType.glob) {
343+
for (final input in node.globNodeState!.inputs) {
344+
result.putIfAbsent(input, () => {}).add(node.id);
345+
}
346+
}
347+
}
348+
return _outputs = result;
349+
}
350+
314351
/// All nodes in the graph, whether source files or generated outputs.
315352
Iterable<AssetNode> get allNodes =>
316353
_nodesByPackage.values.expand((pkdIds) => pkdIds.values);
@@ -408,8 +445,7 @@ class AssetGraph implements GeneratedAssetHider {
408445
.where(
409446
(node) =>
410447
node.isTrackedInput &&
411-
(node.outputs.isNotEmpty ||
412-
node.primaryOutputs.isNotEmpty ||
448+
(node.primaryOutputs.isNotEmpty ||
413449
node.lastKnownDigest != null),
414450
)
415451
.map((node) => node.id),
@@ -458,6 +494,7 @@ class AssetGraph implements GeneratedAssetHider {
458494
// Transitively invalidates all assets. This needs to happen after the
459495
// structure of the graph has been updated.
460496
var invalidatedIds = <AssetId>{};
497+
final computedOutputs = computeOutputs();
461498

462499
void invalidateNodeAndDeps(AssetId startNodeId) {
463500
if (!invalidatedIds.add(startNodeId)) return;
@@ -484,7 +521,8 @@ class AssetGraph implements GeneratedAssetHider {
484521
});
485522

486523
if (invalidatedNode != null) {
487-
for (final id in invalidatedNode.outputs) {
524+
for (final id
525+
in (computedOutputs[invalidatedNode.id] ?? const <AssetId>{})) {
488526
if (invalidatedIds.add(id)) {
489527
nodesToInvalidate.add(id);
490528
}
@@ -534,6 +572,7 @@ class AssetGraph implements GeneratedAssetHider {
534572
}
535573
}
536574

575+
_outputs = null;
537576
return invalidatedIds;
538577
}
539578

@@ -681,12 +720,14 @@ class AssetGraph implements GeneratedAssetHider {
681720
throw ArgumentError('Expected node of type NodeType.builderOptionsNode');
682721
}
683722
var removed = <AssetId>{};
723+
Map<AssetId, Set<AssetId>>? computedOutputsBeforeRemoves;
684724
for (var output in outputs) {
685725
AssetNode? existing;
686726
// When any outputs aren't hidden we can pick up old generated outputs as
687727
// regular `AssetNode`s, we need to delete them and all their primary
688728
// outputs, and replace them with a `GeneratedAssetNode`.
689729
if (contains(output)) {
730+
computedOutputsBeforeRemoves = computeOutputs();
690731
existing = get(output)!;
691732
if (existing.type == NodeType.generated) {
692733
final existingConfiguration = existing.generatedNodeConfiguration!;
@@ -712,13 +753,9 @@ class AssetGraph implements GeneratedAssetHider {
712753
isHidden: isHidden,
713754
);
714755
if (existing != null) {
715-
newNode = newNode.rebuild((b) => b..outputs.addAll(existing!.outputs));
716756
// Ensure we set up the reverse link for NodeWithInput nodes.
717-
_addInput(existing.outputs, output);
757+
_addInput(computedOutputsBeforeRemoves![output] ?? <AssetId>{}, output);
718758
}
719-
updateNode(builderOptionsNode.id, (nodeBuilder) {
720-
nodeBuilder.outputs.add(output);
721-
});
722759
_add(newNode);
723760
}
724761
return removed;

build_runner_core/lib/src/asset_graph/node.dart

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,6 @@ abstract class AssetNode implements Built<AssetNode, AssetNodeBuilder> {
5757
/// when run on this asset.
5858
BuiltSet<AssetId> get primaryOutputs;
5959

60-
/// The [AssetId]s of all generated assets which are output by a [Builder]
61-
/// which reads this asset.
62-
BuiltSet<AssetId> get outputs;
63-
6460
/// The [Digest] for this node in its last known state.
6561
///
6662
/// May be `null` if this asset has no outputs, or if it doesn't actually
@@ -96,7 +92,6 @@ abstract class AssetNode implements Built<AssetNode, AssetNodeBuilder> {
9692
bool get changesRequireRebuild =>
9793
type == NodeType.internal ||
9894
type == NodeType.glob ||
99-
outputs.isNotEmpty ||
10095
lastKnownDigest != null;
10196

10297
factory AssetNode([void Function(AssetNodeBuilder) updates]) = _$AssetNode;
@@ -124,7 +119,6 @@ abstract class AssetNode implements Built<AssetNode, AssetNodeBuilder> {
124119
b.id = id;
125120
b.type = NodeType.source;
126121
b.primaryOutputs.replace(primaryOutputs ?? {});
127-
b.outputs.replace(outputs ?? {});
128122
b.lastKnownDigest = lastKnownDigest;
129123
});
130124

@@ -239,6 +233,19 @@ abstract class AssetNode implements Built<AssetNode, AssetNodeBuilder> {
239233
globNodeState != null,
240234
);
241235
}
236+
237+
/// The generated node inputs, or the glob node inputs, or `null` if the node
238+
/// is not of one of those two types.
239+
BuiltSet<AssetId>? get inputs {
240+
switch (type) {
241+
case NodeType.generated:
242+
return generatedNodeState!.inputs;
243+
case NodeType.glob:
244+
return globNodeState!.inputs;
245+
default:
246+
return null;
247+
}
248+
}
242249
}
243250

244251
/// Additional configuration for an [AssetNode.generated].

0 commit comments

Comments
 (0)