Skip to content

Commit cc39f18

Browse files
committed
Fix invalidation when a non-primary input is deleted.
1 parent 0a8244d commit cc39f18

File tree

7 files changed

+84
-76
lines changed

7 files changed

+84
-76
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: 16 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -277,39 +277,9 @@ class AssetGraph implements GeneratedAssetHider {
277277
for (var output in node.primaryOutputs.toList()) {
278278
_removeRecursive(output, removedIds: removedIds);
279279
}
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);
312-
}
280+
updateNode(id, (nodeBuilder) {
281+
nodeBuilder.replace(AssetNode.missingSource(id));
282+
});
313283

314284
// Remove post build action applications with removed assets as inputs.
315285
for (final packageOutputs in _postProcessBuildStepOutputs.values) {
@@ -401,8 +371,9 @@ class AssetGraph implements GeneratedAssetHider {
401371
/// Updates graph structure, invalidating and deleting any outputs that were
402372
/// affected.
403373
///
404-
/// Returns the list of [AssetId]s that were invalidated.
405-
Future<Set<AssetId>> updateAndInvalidate(
374+
/// Returns the set of [AssetId]s that were deleted, and the set that was
375+
/// invalidated.
376+
Future<(Set<AssetId>, Set<AssetId>)> updateAndInvalidate(
406377
BuildPhases buildPhases,
407378
Map<AssetId, ChangeType> updates,
408379
String rootPackage,
@@ -588,7 +559,7 @@ class AssetGraph implements GeneratedAssetHider {
588559
}
589560

590561
_outputs = null;
591-
return invalidatedIds;
562+
return (idsToDelete, invalidatedIds);
592563
}
593564

594565
/// Crawl up primary inputs to see if the original Source file matches the
@@ -769,9 +740,16 @@ class AssetGraph implements GeneratedAssetHider {
769740
@override
770741
String toString() => allNodes.toList().toString();
771742

772-
// TODO remove once tests are updated
773743
void add(AssetNode node) => _add(node);
774-
Set<AssetId> remove(AssetId id) => _removeRecursive(id);
744+
745+
/// Removes a generated node that was output by a post process build step.
746+
/// TODO(davidmorgan): look at removing them from the graph altogether.
747+
void removePostProcessOutput(AssetId id) {
748+
_nodesByPackage[id.package]!.remove(id.path);
749+
}
750+
751+
void removeForTest(AssetId id) =>
752+
_nodesByPackage[id.package]?.remove(id.path);
775753

776754
/// Adds [input] to all [outputs] if they track inputs.
777755
///

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);

build_runner_core/test/invalidation/asset_input_invalidation_test.dart

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import 'package:test/test.dart';
66

77
import 'invalidation_tester.dart';
88

9-
/// Invalidation tests in which the inputs are individually read arbitrary assets.
9+
/// Invalidation tests in which the inputs are individually read arbitrary
10+
/// assets.
1011
void main() {
1112
late InvalidationTester tester;
1213

@@ -35,10 +36,7 @@ void main() {
3536
await tester.build();
3637
expect(
3738
await tester.build(delete: 'z'),
38-
// TODO(davidmorgan): a.1 should be rebuilt, but it's not. This is a
39-
// regression since the last version on pub: fix it!
40-
// Result(written: ['a.1'], deleted: ['z.1'])
41-
Result(deleted: ['z.1']),
39+
Result(written: ['a.1'], deleted: ['z.1']),
4240
);
4341
});
4442

@@ -73,14 +71,11 @@ void main() {
7371
);
7472
});
7573

76-
test('delete a.1, a.2+b.4 are rebuilt', () async {
74+
test('delete a.1, a.2 is deleted and b.4 is rebuilt', () async {
7775
await tester.build();
7876
expect(
7977
await tester.build(delete: 'a.1'),
80-
// TODO(davidmorgan): a.1 should be rebuilt, but it's not. This is a
81-
// regression since the last version on pub: fix it!
82-
// Result(written: ['a.2', 'b.4']),
83-
Result(deleted: ['a.2']),
78+
Result(deleted: ['a.2'], written: ['b.4']),
8479
);
8580
});
8681

build_runner_core/test/invalidation/invalidation_tester.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import 'dart:async';
66
import 'dart:convert';
77

88
import 'package:build/build.dart';
9-
import 'package:build_runner_core/src/changes/asset_updates.dart';
109
import 'package:build_test/build_test.dart';
1110
import 'package:built_collection/built_collection.dart';
1211
import 'package:crypto/crypto.dart';

0 commit comments

Comments
 (0)