diff --git a/build_runner/test/generate/watch_test.dart b/build_runner/test/generate/watch_test.dart index fc028243d..9ff716f6f 100644 --- a/build_runner/test/generate/watch_test.dart +++ b/build_runner/test/generate/watch_test.dart @@ -22,7 +22,10 @@ import 'package:watcher/watcher.dart'; void main() { /// Basic phases/phase groups which get used in many tests final copyABuildApplication = applyToRoot( - TestBuilder(buildExtensions: appendExtension('.copy', from: '.txt')), + TestBuilder( + buildExtensions: appendExtension('.copy', from: '.txt'), + shouldSkip: (content) => content.contains('skip'), + ), ); final defaultBuilderOptions = const BuilderOptions({}); final packageConfigId = makeAssetId('a|.dart_tool/package_config.json'); @@ -862,6 +865,20 @@ void main() { outputs: {'a|web/a.txt.copy': 'b', 'a|web/a.txt.copy.copy': 'b'}, readerWriter: readerWriter, ); + + await readerWriter.writeAsString(makeAssetId('a|web/a.txt'), 'skip'); + result = await results.next; + checkBuild(result, outputs: {}, readerWriter: readerWriter); + + // Derived outputs should no longer exist. + expect( + readerWriter.testing.exists(makeAssetId('a|web/a.txt.copy')), + isFalse, + ); + expect( + readerWriter.testing.exists(makeAssetId('a|web/a.txt.copy.copy')), + isFalse, + ); }); test('adds propagate through all phases', () async { diff --git a/build_runner_core/lib/src/generate/build_impl.dart b/build_runner_core/lib/src/generate/build_impl.dart index a9f9e759b..a95cfe7ea 100644 --- a/build_runner_core/lib/src/generate/build_impl.dart +++ b/build_runner_core/lib/src/generate/build_impl.dart @@ -445,8 +445,6 @@ class _SingleBuild { if (input.state != NodeState.upToDate) { await _runLazyPhaseForInput(input.phaseNumber, input.primaryInput); } - if (!input.wasOutput) return; - if (input.isFailure) return; } ids.add(input.id); }), @@ -598,24 +596,27 @@ class _SingleBuild { .add(actionDescription); var unusedAssets = {}; - await tracker.trackStage( - 'Build', - () => runBuilder( - builder, - [input], - wrappedReader, - wrappedWriter, - PerformanceTrackingResolvers(_resolvers, tracker), - logger: logger, - resourceManager: _resourceManager, - stageTracker: tracker, - reportUnusedAssetsForInput: - (_, assets) => unusedAssets.addAll(assets), - packageConfig: _packageGraph.asPackageConfig, - ).catchError((void _) { - // Errors tracked through the logger - }), - ); + // Don't run for primary inputs which weren't output. + if (inputNode is! GeneratedAssetNode || inputNode.wasOutput) { + await tracker.trackStage( + 'Build', + () => runBuilder( + builder, + [input], + wrappedReader, + wrappedWriter, + PerformanceTrackingResolvers(_resolvers, tracker), + logger: logger, + resourceManager: _resourceManager, + stageTracker: tracker, + reportUnusedAssetsForInput: + (_, assets) => unusedAssets.addAll(assets), + packageConfig: _packageGraph.asPackageConfig, + ).catchError((void _) { + // Errors tracked through the logger + }), + ); + } actionsCompletedCount++; hungActionsHeartbeat.ping(); pendingActions[phaseNumber]!.remove(actionDescription); diff --git a/build_test/CHANGELOG.md b/build_test/CHANGELOG.md index c301b3431..01f4c6645 100644 --- a/build_test/CHANGELOG.md +++ b/build_test/CHANGELOG.md @@ -14,6 +14,8 @@ - Breaking change: Remove `StubAssetReader`. Use `TestReaderWriter` instead. - Support checks on reader state after a build action in `resolveSources`. - Start using `package:build/src/internal.dart`. +- Add a `shouldSkip` parameter to `TestBuilder`, which allows it to skip certain + inputs based on their file content. - Refactor `BuildCacheReader` to `BuildCacheAssetPathProvider`. ## 2.2.3 diff --git a/build_test/lib/src/builder.dart b/build_test/lib/src/builder.dart index e0cffab60..996b8b1bb 100644 --- a/build_test/lib/src/builder.dart +++ b/build_test/lib/src/builder.dart @@ -13,6 +13,10 @@ typedef BuildBehavior = Map> buildExtensions, ); +/// Optional function to skip a build step based on the content +/// of the primary input. +typedef ShouldSkip = bool Function(String content); + /// Copy the input asset to all possible output assets. Future _defaultBehavior( BuildStep buildStep, @@ -42,12 +46,11 @@ Future _copyToAll( } return Future.wait( - outputs.map( - (id) => buildStep.writeAsString( - id, - read(buildStep, readFrom(buildStep.inputId)), - ), - ), + outputs.map((id) async { + final content = await read(buildStep, readFrom(buildStep.inputId)); + if (content == 'ignore') return; + await buildStep.writeAsString(id, content); + }), ); } @@ -96,6 +99,7 @@ class TestBuilder implements Builder { final BuildBehavior _build; final BuildBehavior? _extraWork; + final ShouldSkip? _shouldSkip; /// A stream of all the [BuildStep.inputId]s that are seen. /// @@ -113,16 +117,25 @@ class TestBuilder implements Builder { Map>? buildExtensions, BuildBehavior? build, BuildBehavior? extraWork, + ShouldSkip? shouldSkip, }) : buildExtensions = buildExtensions ?? appendExtension('.copy'), _build = build ?? _defaultBehavior, - _extraWork = extraWork; + _extraWork = extraWork, + _shouldSkip = shouldSkip; @override Future build(BuildStep buildStep) async { if (!await buildStep.canRead(buildStep.inputId)) return; _buildInputsController.add(buildStep.inputId); - await _build(buildStep, buildExtensions); - await _extraWork?.call(buildStep, buildExtensions); - _buildsCompletedController.add(buildStep.inputId); + try { + if (_shouldSkip != null) { + final content = await buildStep.readAsString(buildStep.inputId); + if (_shouldSkip(content)) return; + } + await _build(buildStep, buildExtensions); + await _extraWork?.call(buildStep, buildExtensions); + } finally { + _buildsCompletedController.add(buildStep.inputId); + } } }