Skip to content

Commit d0349c3

Browse files
authored
More inputs invalidation test; fix for deletions (#3987)
* Add invalidation test coverage for other inputs. * Fix invalidation when a non-primary input is deleted.
1 parent 0a20e64 commit d0349c3

File tree

7 files changed

+211
-91
lines changed

7 files changed

+211
-91
lines changed

build_runner/test/generate/watch_test.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,10 @@ void main() {
361361
readerWriter,
362362
);
363363

364+
var aTxtId = makeAssetId('a|web/a.txt');
365+
var aTxtNode = AssetNode.missingSource(aTxtId);
366+
var aTxtCopyId = makeAssetId('a|web/a.txt.copy');
367+
var aTxtCopyNode = AssetNode.missingSource(aTxtCopyId);
364368
var bCopyId = makeAssetId('a|web/b.txt.copy');
365369
var bTxtId = makeAssetId('a|web/b.txt');
366370
var bCopyNode = AssetNode.generated(
@@ -374,7 +378,10 @@ void main() {
374378
inputs: [makeAssetId('a|web/b.txt')],
375379
isHidden: false,
376380
);
381+
377382
expectedGraph
383+
..add(aTxtNode)
384+
..add(aTxtCopyNode)
378385
..add(bCopyNode)
379386
..add(
380387
makeAssetNode('a|web/b.txt', [

build_runner_core/lib/src/asset_graph/graph.dart

Lines changed: 32 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,6 @@ class AssetGraph implements GeneratedAssetHider {
201201
var existing = get(node.id);
202202
if (existing != null) {
203203
if (existing.type == NodeType.missingSource) {
204-
// Don't call _removeRecursive, that recursively removes all transitive
205-
// primary outputs. We only want to remove this node.
206204
_nodesByPackage[existing.id.package]!.remove(existing.id.path);
207205
node = node.rebuild((b) {
208206
b.primaryOutputs.addAll(existing.primaryOutputs);
@@ -263,60 +261,23 @@ class AssetGraph implements GeneratedAssetHider {
263261
);
264262
}
265263

266-
/// Removes the node representing [id] from the graph, and all of its
267-
/// `primaryOutput`s.
268-
///
269-
/// Also removes all edges between all removed nodes and remaining nodes.
270-
///
271-
/// Returns the IDs of removed asset nodes.
272-
Set<AssetId> _removeRecursive(AssetId id, {Set<AssetId>? removedIds}) {
264+
/// Changes [id] and its transitive`primaryOutput`s to `missingSource` nodes.
265+
void _setMissingRecursive(AssetId id, {Set<AssetId>? removedIds}) {
273266
removedIds ??= <AssetId>{};
274267
var node = get(id);
275-
if (node == null) return removedIds;
268+
if (node == null) return;
276269
removedIds.add(id);
277270
for (var output in node.primaryOutputs.toList()) {
278-
_removeRecursive(output, removedIds: removedIds);
279-
}
280-
final outputs = computeOutputs();
281-
for (var output in (outputs[node.id] ?? const <AssetId>{})) {
282-
updateNodeIfPresent(output, (nodeBuilder) {
283-
if (nodeBuilder.type == NodeType.generated) {
284-
nodeBuilder.generatedNodeState.inputs.remove(id);
285-
} else if (nodeBuilder.type == NodeType.glob) {
286-
nodeBuilder.globNodeState
287-
..inputs.remove(id)
288-
..results.remove(id);
289-
}
290-
});
291-
}
292-
293-
if (node.type == NodeType.generated) {
294-
for (var input in node.generatedNodeState!.inputs) {
295-
// We may have already removed this node entirely.
296-
updateNodeIfPresent(input, (nodeBuilder) {
297-
nodeBuilder.primaryOutputs.remove(id);
298-
});
299-
}
300-
} else if (node.type == NodeType.glob) {
301-
for (var input in node.globNodeState!.inputs) {
302-
// We may have already removed this node entirely.
303-
updateNodeIfPresent(input, (nodeBuilder) {
304-
nodeBuilder.primaryOutputs.remove(id);
305-
});
306-
}
307-
}
308-
309-
// Missing source nodes need to be kept to retain dependency tracking.
310-
if (node.type != NodeType.missingSource) {
311-
_nodesByPackage[id.package]!.remove(id.path);
271+
_setMissingRecursive(output, removedIds: removedIds);
312272
}
273+
updateNode(id, (nodeBuilder) {
274+
nodeBuilder.replace(AssetNode.missingSource(id));
275+
});
313276

314277
// Remove post build action applications with removed assets as inputs.
315278
for (final packageOutputs in _postProcessBuildStepOutputs.values) {
316279
packageOutputs.removeWhere((id, _) => removedIds!.contains(id.input));
317280
}
318-
319-
return removedIds;
320281
}
321282

322283
/// Computes node outputs: the inverse of the graph described by the `inputs`
@@ -401,8 +362,12 @@ class AssetGraph implements GeneratedAssetHider {
401362
/// Updates graph structure, invalidating and deleting any outputs that were
402363
/// affected.
403364
///
404-
/// Returns the list of [AssetId]s that were invalidated.
405-
Future<Set<AssetId>> updateAndInvalidate(
365+
/// Outputs that are deleted from the filesystem are retained in the graph as
366+
/// `missingSource` nodes.
367+
///
368+
/// Returns the set of [AssetId]s that were deleted, and the set that was
369+
/// invalidated.
370+
Future<(Set<AssetId>, Set<AssetId>)> updateAndInvalidate(
406371
BuildPhases buildPhases,
407372
Map<AssetId, ChangeType> updates,
408373
String rootPackage,
@@ -572,23 +537,22 @@ class AssetGraph implements GeneratedAssetHider {
572537
}
573538
}
574539

575-
// Delete all the invalidated assets, then remove them from the graph. This
576-
// order is important because some `AssetWriter`s throw if the id is not in
577-
// the graph.
540+
// Delete all the invalidated assets.
578541
await Future.wait(idsToDelete.map(delete));
579542

580-
// Remove all deleted source assets from the graph, which also recursively
581-
// removes all their primary outputs.
543+
// Change deleted source assets and their transitive primary outputs to
544+
// `missingSource` nodes, rather than deleting them. This allows them to
545+
// remain referenced in `inputs` in order to trigger rebuilds if necessary.
582546
for (final id in removeIds) {
583547
final node = get(id);
584548
if (node != null && node.type == NodeType.source) {
585549
invalidateNodeAndDeps(id);
586-
_removeRecursive(id);
550+
_setMissingRecursive(id);
587551
}
588552
}
589553

590554
_outputs = null;
591-
return invalidatedIds;
555+
return (idsToDelete, invalidatedIds);
592556
}
593557

594558
/// Crawl up primary inputs to see if the original Source file matches the
@@ -713,9 +677,9 @@ class AssetGraph implements GeneratedAssetHider {
713677
///
714678
/// If there are existing [AssetNode.source]s or [AssetNode.missingSource]s
715679
/// that overlap the [AssetNode.generated]s, then they will be replaced with
716-
/// [AssetNode.generated]s, and all their `primaryOutputs` will be removed
717-
/// from from the graph as well. The return value is the set of assets that
718-
/// were removed from the graph.
680+
/// [AssetNode.generated]s, and their transitive `primaryOutputs` will be
681+
/// changed to `missingSource` nodes. The return value is the set of assets
682+
/// that were removed from the graph.
719683
Set<AssetId> _addGeneratedOutputs(
720684
Iterable<AssetId> outputs,
721685
int phaseNumber,
@@ -745,7 +709,7 @@ class AssetGraph implements GeneratedAssetHider {
745709
buildPhases.inBuildPhases[phaseNumber].builderLabel,
746710
);
747711
}
748-
_removeRecursive(output, removedIds: removed);
712+
_setMissingRecursive(output, removedIds: removed);
749713
}
750714

751715
var newNode = AssetNode.generated(
@@ -769,9 +733,16 @@ class AssetGraph implements GeneratedAssetHider {
769733
@override
770734
String toString() => allNodes.toList().toString();
771735

772-
// TODO remove once tests are updated
773736
void add(AssetNode node) => _add(node);
774-
Set<AssetId> remove(AssetId id) => _removeRecursive(id);
737+
738+
/// Removes a generated node that was output by a post process build step.
739+
/// TODO(davidmorgan): look at removing them from the graph altogether.
740+
void removePostProcessOutput(AssetId id) {
741+
_nodesByPackage[id.package]!.remove(id.path);
742+
}
743+
744+
void removeForTest(AssetId id) =>
745+
_nodesByPackage[id.package]?.remove(id.path);
775746

776747
/// Adds [input] to all [outputs] if they track inputs.
777748
///

build_runner_core/lib/src/generate/build.dart

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ class Build {
7777
/// Filled from the `updates` passed in to the build.
7878
final Set<AssetId> changedInputs = {};
7979

80+
/// Assets that were deleted since the last build.
81+
///
82+
/// This is used to distinguish between `missingSource` nodes that were
83+
/// already missing and `missingSource` nodes that are newly missing.
84+
final Set<AssetId> deletedAssets = {};
85+
8086
/// Outputs that changed since the last build.
8187
///
8288
/// Filled during the build as each output is produced and its digest is
@@ -169,14 +175,23 @@ class Build {
169175

170176
Future<void> _updateAssetGraph(Map<AssetId, ChangeType> updates) async {
171177
await logTimedAsync(_logger, 'Updating asset graph', () async {
172-
changedInputs.addAll(updates.keys.toSet());
173-
var invalidated = await assetGraph.updateAndInvalidate(
178+
changedInputs.clear();
179+
deletedAssets.clear();
180+
for (final update in updates.entries) {
181+
if (update.value == ChangeType.REMOVE) {
182+
deletedAssets.add(update.key);
183+
} else {
184+
changedInputs.add(update.key);
185+
}
186+
}
187+
final (deleted, invalidated) = await assetGraph.updateAndInvalidate(
174188
buildPhases,
175189
updates,
176190
options.packageGraph.root.name,
177191
_delete,
178192
readerWriter,
179193
);
194+
deletedAssets.addAll(deleted);
180195
await readerWriter.cache.invalidate(invalidated);
181196
});
182197
}
@@ -626,7 +641,7 @@ class Build {
626641
);
627642
await _cleanUpStaleOutputs(existingOutputs);
628643
for (final output in existingOutputs) {
629-
assetGraph.remove(output);
644+
assetGraph.removePostProcessOutput(output);
630645
}
631646
assetGraph.updateNode(inputNode.id, (nodeBuilder) {
632647
nodeBuilder.deletedBy.remove(postProcessBuildStepId);
@@ -842,6 +857,16 @@ class Build {
842857
}
843858
return true;
844859
}
860+
} else if (node.type == NodeType.missingSource) {
861+
if (deletedAssets.contains(input)) {
862+
if (logFine) {
863+
_logger.fine(
864+
'Build ${renderer.build(forInput, outputs)} because '
865+
'${renderer.id(input)} was deleted.',
866+
);
867+
}
868+
return true;
869+
}
845870
}
846871
}
847872

build_runner_core/test/asset_graph/graph_test.dart

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ void main() {
7171
nodes.add(testAddNode());
7272
}
7373
graph
74-
..remove(nodes[1].id)
75-
..remove(nodes[4].id);
74+
..removeForTest(nodes[1].id)
75+
..removeForTest(nodes[4].id);
7676

7777
expectNodeExists(nodes[0]);
7878
expectNodeDoesNotExist(nodes[1]);
@@ -82,7 +82,7 @@ void main() {
8282

8383
graph
8484
// Doesn't throw.
85-
..remove(nodes[1].id)
85+
..removeForTest(nodes[1].id)
8686
// Can be added back
8787
..add(nodes[1]);
8888
expectNodeExists(nodes[1]);
@@ -306,8 +306,8 @@ void main() {
306306
(id) async => deletes.add(id),
307307
digestReader,
308308
);
309-
expect(graph.contains(primaryInputId), isFalse);
310-
expect(graph.contains(primaryOutputId), isFalse);
309+
expect(graph.get(primaryInputId)!.type, NodeType.missingSource);
310+
expect(graph.get(primaryOutputId)!.type, NodeType.missingSource);
311311
expect(deletes, equals([primaryOutputId]));
312312
expect(graph.postProcessBuildStepIds(package: 'foo'), isEmpty);
313313
});
@@ -414,8 +414,8 @@ void main() {
414414
digestReader,
415415
);
416416

417-
expect(graph.contains(primaryInputId), isFalse);
418-
expect(graph.contains(primaryOutputId), isFalse);
417+
expect(graph.get(primaryInputId)!.type, NodeType.missingSource);
418+
expect(graph.get(primaryOutputId)!.type, NodeType.missingSource);
419419
expect(
420420
graph.computeOutputs()[secondaryId] ?? const <AssetId>{},
421421
isNot(contains(primaryOutputId)),
@@ -513,14 +513,6 @@ void main() {
513513
expect(globNode.globNodeState!.inputs, contains(coolAssetId));
514514
expect(globNode.globNodeState!.results, contains(coolAssetId));
515515
await checkChangeType(ChangeType.REMOVE);
516-
expect(
517-
globNode.globNodeState!.inputs,
518-
isNot(contains(coolAssetId)),
519-
);
520-
expect(
521-
globNode.globNodeState!.results,
522-
isNot(contains(coolAssetId)),
523-
);
524516
},
525517
);
526518
});
@@ -678,16 +670,25 @@ void main() {
678670
..inputs.add(outputReadingNode);
679671
});
680672

681-
final invalidatedNodes = await graph.updateAndInvalidate(
673+
await graph.updateAndInvalidate(
682674
buildPhases,
683675
{makeAssetId('foo|lib/a.txt'): ChangeType.ADD},
684676
'foo',
685677
(_) async {},
686678
digestReader,
687679
);
688680

689-
expect(invalidatedNodes, contains(outputReadingNode));
690-
expect(invalidatedNodes, contains(lastPrimaryOutputNode));
681+
expect(
682+
graph.get(outputReadingNode)!.generatedNodeState!.pendingBuildAction,
683+
PendingBuildAction.buildIfInputsChanged,
684+
);
685+
expect(
686+
graph
687+
.get(lastPrimaryOutputNode)!
688+
.generatedNodeState!
689+
.pendingBuildAction,
690+
PendingBuildAction.buildIfInputsChanged,
691+
);
691692
});
692693

693694
test('https://github.com/dart-lang/build/issues/1804', () async {
@@ -736,8 +737,8 @@ void main() {
736737
digestReader,
737738
);
738739

739-
// The old generated part file should no longer exist
740-
expect(graph.get(generatedPart), isNull);
740+
// The old generated part file should be marked as missing.
741+
expect(graph.get(generatedPart)!.type, NodeType.missingSource);
741742

742743
// The generated part file should not exist in outputs of the new
743744
// generated dart file

build_runner_core/test/generate/build_test.dart

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1673,7 +1673,10 @@ void main() {
16731673
makeAssetId('a|$assetGraphPath'),
16741674
),
16751675
);
1676-
expect(graph.get(makeAssetId('a|lib/a.txt.copy')), isNull);
1676+
expect(
1677+
graph.get(makeAssetId('a|lib/a.txt.copy'))!.type,
1678+
NodeType.missingSource,
1679+
);
16771680
},
16781681
);
16791682
});
@@ -1702,15 +1705,15 @@ void main() {
17021705
resumeFrom: result,
17031706
);
17041707

1705-
/// Should be deleted using the writer, and removed from the new graph.
1708+
/// Should be deleted using the writer, and converted to missingSource.
17061709
var newGraph = AssetGraph.deserialize(
17071710
result.readerWriter.testing.readBytes(makeAssetId('a|$assetGraphPath')),
17081711
);
17091712
var aNodeId = makeAssetId('a|lib/a.txt');
17101713
var aCopyNodeId = makeAssetId('a|lib/a.txt.copy');
17111714
var aCloneNodeId = makeAssetId('a|lib/a.txt.copy.clone');
1712-
expect(newGraph.contains(aNodeId), isFalse);
1713-
expect(newGraph.contains(aCopyNodeId), isFalse);
1715+
expect(newGraph.get(aNodeId)!.type, NodeType.missingSource);
1716+
expect(newGraph.get(aCopyNodeId)!.type, NodeType.missingSource);
17141717
expect(newGraph.contains(aCloneNodeId), isFalse);
17151718
expect(result.readerWriter.testing.exists(aNodeId), isFalse);
17161719
expect(result.readerWriter.testing.exists(aCopyNodeId), isFalse);

0 commit comments

Comments
 (0)