Skip to content

DX enhancement: auto-restart option for build_runner watch on package graph update #3923

@tgrushka

Description

@tgrushka

I work on many different Dart and Flutter projects and often use build_runner to manage generated code.

When build_runner watch detects a graph update and outputs the following:

[INFO] Caching finalized dependency graph...
[INFO] Caching finalized dependency graph completed, took 61ms

[INFO] Succeeded after 91ms with 0 outputs (0 actions)

(Update pubspec.yaml and run dart pub get in a different terminal here...)

[SEVERE] Terminating builds due to package graph update, please restart the build.
[INFO] Terminating. No further builds will be scheduled

[INFO] Builds finished. Safe to exit

(Terminates with exit code 0.)

This behavior forces developers to experience the following:

  1. While we are busy refactoring our code, suddenly things that depend on generated code have errors, or we don't see properties or methods that were supposed to be generated. We see a lot of red in the editor, have to switch to the build_runner watch terminal to see what happened, and see that the process has exited.
  2. Re-run build_runner watch, rinse, and repeat.
  3. Finally, when we get tired of this, go through several iterations of shell scripts, because build_runner exits 0 whether it was terminated with Control-C (SIGINT) or for the above reason. So we cannot determine via exit code the reason for terminating, so we can't just loop and re-run it forever, because that would also put us in an endless loop, even when we want to exit.
  • Shouldn't a SEVERE error as shown in the console at least return an error status (maybe 1 instead of 0)?

Yes, I have technically "solved" the problem for myself with the following script that I can drop into just or whatever process I use, but I must do this for every Dart/Flutter project, or create a general bash function in my environment that I can invoke to wrap build_runner:

#!/usr/bin/env bash

set -o pipefail

trap 'echo "Exiting..."; exit 1' INT

while true; do
    tmpfile=$(mktemp)
    dart run build_runner watch -d 2>&1 | tee $tmpfile
    if grep -q "No further builds will be scheduled" $tmpfile; then
        rm $tmpfile
        echo "RESTARTING build_runner."
        continue
    fi
    rm $tmpfile
    sleep 1
done

But it took me several iterations, and having to tee and grep a temp file because the exit status is always 0. I argue that this experience:

  • interrupts developers' workflows;
  • confuses developers when their builds silently stop running;
  • wastes time and energy developing and maintaining wrapper scripts that they shouldn't have to.

I recall reading somewhere in this repo that there were technical reasons for terminating the build process on dependency graph update. I appreciate and respect that.

I also argue that trying to "fix" such technical limitations to achieve "incremental" rebuilds across dependency graph changes, e.g. trying to "diff" two different dependency graphs (I'm no Dart internals expert, only guessing), is probably way overkill for the minimal speed/performance gain it might achieve. Using my script above, sure, a rebuild may take longer than an incremental build, but I don't even notice it, as it almost always happens faster than I can update my dependencies and switch to any code in my editor that depends on the build.

So, the big question is: would it be possible to just "restart" the build by default from a "fresh" state, as if the user just re-ran the command, so that we wouldn't have to use wrapper scripts? Maybe there are edge cases in which it would error out, but why can't it just log the errors and wait for the developer to update their code again? And there could always be an option to revert to the old behavior.

The reason I ask for a change in the default behavior, as opposed to just adding another flag, is that I believe it is a normal developer expectation that when they run "watch", the watch just continuously runs until interrupted. Further, we already have so many flags to keep track of, like --delete-conflicting-outputs / -d.

I would be happy to be a tester of this feature or provide any assistance that I could. I have a lot of experience in Dart apps, but not its internals. Due to this, I would be happy to attempt and do a PR, but the style or method of my solution may not be in line with Dart authors' expectations. Thank you for your consideration.

Metadata

Metadata

Assignees

No one assigned

    Labels

    type-enhancementA request for a change that isn't a bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions