Description
I am investigating this build_runner error when using Jaspr. After some digging I found a reproducible setup and think its a general build_runner bug. It boils down to the following:
I have two builders:
- Builder A reads in
.a
and generates.b
(e.g.main.a
->main.b
) - Builder B reads in
.b
and generates.c
(e.g.main.b
->main.c
)
Builder A has some logic that it only conditionally generates .b
files and that condition might change while watching the files (dart run build_runner watch
).
When that happens, Builder A no longer outputs main.b
. At that point I expect build_runner to also cleanup main.c
since Builder B is also no longer executed since it doesn't have any input. However main.c
still persists with now outdated contents.
To reproduce do the following steps:
- Create a blank dart project.
- Add
build
as a dep andbuild_runner
as a dev dep. - Add the following files:
build.yaml
builders:
a:
import: "package:buildtest/builders.dart"
builder_factories:
- aBuilder
build_extensions:
.a:
- .b
auto_apply: all_packages
build_to: source
required_inputs: [".a"]
b:
import: "package:buildtest/builders.dart"
builder_factories:
- bBuilder
build_extensions:
.b:
- .c
auto_apply: all_packages
build_to: source
required_inputs: [".b"]
lib/builders.dart
import 'dart:async';
import 'package:build/build.dart';
Builder aBuilder(BuilderOptions options) => ABuilder();
Builder bBuilder(BuilderOptions options) => BBuilder();
class ABuilder extends Builder {
@override
FutureOr<void> build(BuildStep buildStep) async {
var content = await buildStep.readAsString(buildStep.inputId);
if (content.contains('on')) {
await buildStep.writeAsString(buildStep.inputId.changeExtension('.b'), content);
}
}
@override
Map<String, List<String>> get buildExtensions => {
'.a': ['.b'],
};
}
class BBuilder extends Builder {
@override
FutureOr<void> build(BuildStep buildStep) async {
var content = await buildStep.readAsString(buildStep.inputId);
await buildStep.writeAsString(buildStep.inputId.changeExtension('.c'), content);
}
@override
Map<String, List<String>> get buildExtensions => {
'.b': ['.c'],
};
}
lib/test.a
test on
- Run
dart run build_runner watch -v
- See
lib/test.b
andlib/test.c
being generated. - Change
lib/test.a
totest off
- See
lib/test.b
correctly being removed, butlib/test.c
wrongly being still there.
In the real-world case I experienced, BuilderA is a custom builder by Jaspr that generates web/<filename>.dart
files that should be compiled to js. BuilderB is ddc_modules
from package:build_web_compilers
that outputs .ddc.module
files for each .dart
file. The error occurs in the ddc
builder that for each .ddc.module
tries to read the respective .dart
file, which doesn't exist for an outdated module.
You can reproduce the error by creating a new Jaspr project (jaspr create testapp
), starting build_runner watch
and removing the @client
annotation from e.g. lib/pages/about.dart
.
dart --version
: Dart SDK version: 3.7.0 (stable) (Wed Feb 5 04:53:58 2025 -0800) on "macos_arm64"
build_runner: ^2.4.15
(latest)
build: ^2.4.2
(latest)