Skip to content

Commit c7b2fc7

Browse files
authored
Fix handling of checked in outputs. (#4007)
1 parent 2e0d928 commit c7b2fc7

File tree

5 files changed

+107
-16
lines changed

5 files changed

+107
-16
lines changed

build_runner_core/lib/src/asset_graph/graph.dart

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,8 @@ class AssetGraph implements GeneratedAssetHider {
261261
}
262262

263263
/// Changes [id] and its transitive`primaryOutput`s to `missingSource` nodes.
264+
///
265+
/// Removes post build applications with removed assets as inputs.
264266
void _setMissingRecursive(AssetId id, {Set<AssetId>? removedIds}) {
265267
removedIds ??= <AssetId>{};
266268
var node = get(id);
@@ -279,6 +281,19 @@ class AssetGraph implements GeneratedAssetHider {
279281
}
280282
}
281283

284+
/// Removes [id] and its transitive`primaryOutput`s from the graph.
285+
void _removeRecursive(AssetId id, {Set<AssetId>? removedIds}) {
286+
removedIds ??= <AssetId>{};
287+
var node = get(id);
288+
if (node == null) return;
289+
if (removedIds.add(id)) {
290+
for (var output in node.primaryOutputs.toList()) {
291+
_removeRecursive(output, removedIds: removedIds);
292+
}
293+
_nodesByPackage[id.package]!.remove(id.path);
294+
}
295+
}
296+
282297
/// Computes node outputs: the inverse of the graph described by the `inputs`
283298
/// fields on glob and generated nodes.
284299
///
@@ -557,8 +572,9 @@ class AssetGraph implements GeneratedAssetHider {
557572
/// If there are existing [AssetNode.source]s or [AssetNode.missingSource]s
558573
/// that overlap the [AssetNode.generated]s, then they will be replaced with
559574
/// [AssetNode.generated]s, and their transitive `primaryOutputs` will be
560-
/// changed to `missingSource` nodes. The return value is the set of assets
561-
/// that were removed from the graph.
575+
/// removed from the graph.
576+
///
577+
/// The return value is the set of assets that were removed from the graph.
562578
Set<AssetId> _addGeneratedOutputs(
563579
Iterable<AssetId> outputs,
564580
int phaseNumber,
@@ -588,7 +604,7 @@ class AssetGraph implements GeneratedAssetHider {
588604
buildPhases.inBuildPhases[phaseNumber].builderLabel,
589605
);
590606
}
591-
_setMissingRecursive(output, removedIds: removed);
607+
_removeRecursive(output, removedIds: removed);
592608
}
593609

594610
var newNode = AssetNode.generated(
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:test/test.dart';
6+
7+
import 'invalidation_tester.dart';
8+
9+
void main() {
10+
late InvalidationTester tester;
11+
12+
setUp(() {
13+
tester = InvalidationTester();
14+
15+
// Start with output source on disk.
16+
//
17+
// The ordering of sources matters because a different codepath in
18+
// `graph.dart` is triggered depending on whether a source is first
19+
// processed as an input or as a generated output of another input.
20+
//
21+
// So for `a` have the output come first, and for `b` the input come
22+
// first.
23+
tester.sources(['a.g', 'a', 'b', 'b.g']);
24+
});
25+
26+
group('a <== a.g, a <== a.other', () {
27+
setUp(() {
28+
tester.builder(from: '', to: '.g', outputIsVisible: true)
29+
..reads('')
30+
..writes('.g');
31+
32+
tester.builder(from: '', to: '.other', outputIsVisible: true)
33+
..reads('')
34+
..writes('.other');
35+
});
36+
37+
test('checked in outputs are not treated as inputs', () async {
38+
expect(
39+
await tester.build(),
40+
// If outputs were treated by inputs there would be outputs created like
41+
// `a.g.g.other`.
42+
Result(
43+
written: [
44+
'a.g',
45+
'a.other',
46+
'a.g.other',
47+
'b.g',
48+
'b.other',
49+
'b.g.other',
50+
],
51+
),
52+
);
53+
});
54+
});
55+
}

build_runner_core/test/invalidation/invalidation_tester.dart

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,9 @@ class InvalidationTester {
8383
required String from,
8484
required String to,
8585
bool isOptional = false,
86+
bool outputIsVisible = true,
8687
}) {
87-
final builder = TestBuilder(this, from, [to], isOptional);
88+
final builder = TestBuilder(this, from, [to], isOptional, outputIsVisible);
8889
_builders.add(builder);
8990
return TestBuilderBuilder(builder);
9091
}
@@ -185,6 +186,7 @@ class InvalidationTester {
185186
assets.map((id, content) => MapEntry(id.toString(), content)),
186187
rootPackage: 'pkg',
187188
optionalBuilders: _builders.where((b) => b.isOptional).toSet(),
189+
visibleOutputBuilders: _builders.where((b) => b.outputIsVisible).toSet(),
188190
testingBuilderConfig: false,
189191
);
190192
final logString = log.toString();
@@ -309,12 +311,16 @@ class TestBuilder implements Builder {
309311

310312
final bool isOptional;
311313

314+
final bool outputIsVisible;
315+
312316
final InvalidationTester _tester;
313317

318+
final String from;
319+
314320
/// Extensions of assets that the builder will read.
315321
///
316322
/// The extensions are applied to the primary input asset ID with
317-
/// [AssetIdExtension.replaceAllPathExtensions].
323+
/// [AssetIdExtension.replaceExtensions].
318324
List<String> reads = [];
319325

320326
/// Names of assets that the builder will read.
@@ -326,17 +332,22 @@ class TestBuilder implements Builder {
326332
/// Extensions of assets that the builder will write.
327333
///
328334
/// The extensions are applied to the primary input asset ID with
329-
/// [AssetIdExtension.replaceAllPathExtensions].
335+
/// [AssetIdExtension.replaceExtensions].
330336
List<String> writes = [];
331337

332-
TestBuilder(this._tester, String from, Iterable<String> to, this.isOptional)
333-
: buildExtensions = {'$from.dart': to.map((to) => '$to.dart').toList()};
338+
TestBuilder(
339+
this._tester,
340+
this.from,
341+
Iterable<String> to,
342+
this.isOptional,
343+
this.outputIsVisible,
344+
) : buildExtensions = {'$from.dart': to.map((to) => '$to.dart').toList()};
334345

335346
@override
336347
Future<void> build(BuildStep buildStep) async {
337348
final content = <String>[];
338349
for (final read in reads) {
339-
final readId = buildStep.inputId.replaceAllPathExtensions(read);
350+
final readId = buildStep.inputId.replaceExtensions('$from.dart', read);
340351
content.add(read.toString());
341352
if (await buildStep.canRead(readId)) {
342353
content.add(await buildStep.readAsString(readId));
@@ -353,7 +364,7 @@ class TestBuilder implements Builder {
353364
await buildStep.resolver.libraryFor(resolve.assetId);
354365
}
355366
for (final write in writes) {
356-
final writeId = buildStep.inputId.replaceAllPathExtensions(write);
367+
final writeId = buildStep.inputId.replaceExtensions('$from.dart', write);
357368
final outputStrategy =
358369
_tester._outputStrategies[writeId] ?? OutputStrategy.inputDigest;
359370
final inputHash = base64.encode(
@@ -371,7 +382,7 @@ class TestBuilder implements Builder {
371382
}
372383
}
373384
for (final write in writes) {
374-
final writeId = buildStep.inputId.replaceAllPathExtensions(write);
385+
final writeId = buildStep.inputId.replaceExtensions('$from.dart', write);
375386
if (_tester._failureStrategies[writeId] == FailureStrategy.fail) {
376387
throw StateError('Failing as requested by test setup.');
377388
}
@@ -417,8 +428,6 @@ String _assetIdToName(AssetId id) {
417428
}
418429

419430
extension AssetIdExtension on AssetId {
420-
static final extensionsRegexp = RegExp(r'\..*');
421-
422-
AssetId replaceAllPathExtensions(String extension) =>
423-
AssetId(package, path.replaceAll(extensionsRegexp, '') + extension);
431+
AssetId replaceExtensions(String from, String to) =>
432+
AssetId(package, path.replaceAll(RegExp('$from\$'), to));
424433
}

build_test/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
- `resolveSources` and `testBuilder` now do a full `build_runner` build, with
66
configuration as much as possible based on the some parameters.
77
- Add `testBuilders` to run a test build with multiple builders.
8-
- Add `optionalBuilder` to `testBuilders` to have some builders be optional.
8+
- Add `optionalBuilders` to `testBuilders` to have some builders be optional.
9+
- Add `visibleOutputBuilders` to `testBuilders` to have some builders write
10+
their output next to their inputs.
911
- Add `testingBuilderConfig` to `testBuilders` to control builder config
1012
override.
1113
- Add `resolvers` parameter to `testBuild` and `testBuilders`.

build_test/lib/src/test_builder.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,10 @@ Future<TestBuilderResult> testBuilder(
196196
/// To mark a builder as optional, add it to [optionalBuilders]. Optional
197197
/// builders only run if their output is used by a non-optional builder.
198198
///
199+
/// To mark a builder's output as visible, add it to [visibleOutputBuilders].
200+
/// The builder then writes its outputs next to its input, instead of hidden
201+
/// under `.dart_tool`.
202+
///
199203
/// The default builder config will be overwritten with one that causes the
200204
/// builder to run for all inputs. To use the default builder config instead,
201205
/// set [testingBuilderConfig] to `false`.
@@ -221,6 +225,7 @@ Future<TestBuilderResult> testBuilders(
221225
PackageConfig? packageConfig,
222226
Resolvers? resolvers,
223227
Set<Builder> optionalBuilders = const {},
228+
Set<Builder> visibleOutputBuilders = const {},
224229
bool testingBuilderConfig = true,
225230
TestReaderWriter? readerWriter,
226231
bool enableLowResourceMode = false,
@@ -322,6 +327,9 @@ Future<TestBuilderResult> testBuilders(
322327
// didn't change. Skip it to allow testing with preserved state.
323328
skipBuildScriptCheck: true,
324329
enableLowResourcesMode: enableLowResourceMode,
330+
// If a builder has visible output, it might need to be deleted. Do so
331+
// without prompting.
332+
deleteFilesByDefault: true,
325333
);
326334

327335
final buildSeries = await BuildSeries.create(buildOptions, environment, [
@@ -331,6 +339,7 @@ Future<TestBuilderResult> testBuilders(
331339
[(_) => builder],
332340
(package) => package.name != r'$sdk',
333341
isOptional: optionalBuilders.contains(builder),
342+
hideOutput: !visibleOutputBuilders.contains(builder),
334343
),
335344
], {});
336345

0 commit comments

Comments
 (0)