When the per-package files produced by build hooks (e.g. DataAsset files) are removed from disk, flutter run and flutter build print Skipping target: build_hooks and proceed to the asset-bundling step, which then fails. The hook target is treated as up-to-date even though its real outputs are gone.
The cause is narrow: the existing LinkHooks target already declares hook-produced asset files as outputs in its depfile, but the BuildHooks target does not. Mirroring the LinkHooks pattern in BuildHooks closes the gap, and the existing outputMissing check at build_system/build_system.dart:1241-1246 handles the rest at no additional cost.
Steps to reproduce
- Use any project where a build hook registers a
DataAsset. Run it once successfully so the hook caches a fresh build.
- Manually delete one of the produced asset files.
flutter run -d <device>.
Observed (verbose):
Skipping target: build_hooks
...
Error detected in pubspec.yaml:
No file or variants found for asset: ...
Target debug_android_application failed: Exception: Failed to bundle asset files.
flutter clean does not help because it wipes build/ but leaves the .dart_tool/flutter_build/<hash>/native_assets.json cache, which still claims inputs are unchanged.
Where the asymmetry is
packages/flutter_tools/lib/src/build_system/targets/native_assets.dart.
LinkHooks.build includes hook-produced files in its depfile outputs at line 228-234:
final depfile = Depfile(
<File>[for (final Uri dependency in result.dependencies) fileSystem.file(dependency)],
<File>[
fileSystem.file(dartHookResultJsonFile),
for (final Uri uri in result.filesToBeBundled) fileSystem.file(uri),
],
);
BuildHooks.build does not, line 79-82:
final depfile = Depfile(
<File>[for (final Uri dependency in dependencies) fileSystem.file(dependency)],
<File>[fileSystem.file(dartBuildOutputJsonFile)],
);
So when a build hook produces a DataAsset and the consuming app later loses the asset file, BuildHooks' cache keeps claiming up-to-date because the only output it knows about (build_hooks_result.json) is still present.
Suggested fix
In BuildHooks.build, fold the hook's bundled files into the depfile outputs, matching the LinkHooks shape. The BuildResult from the hook protocol already carries the asset file URIs (the same filesToBeBundled view LinkHooks consumes), so this is plumbing that already exists at the layer above; it just needs to be propagated into the depfile.
A unit test should cover: BuildHooks runs once, declared output is deleted from disk, second run sees outputMissing for that path and re-runs.
Note for hook authors
This fix only catches missing outputs that were registered as hook-produced assets in the first place (typically DataAsset via output.assets.addEncodedAsset(DataAsset(...).encode())). Hooks that write into the consumer's build/ directory and rely on the consumer's pubspec flutter: assets: block sit outside this protocol and won't benefit; those hooks should migrate to registering their outputs through the hooks API for both correctness reasons (this) and asset path consistency (packages/<pkg>/<name> instead of consumer-relative paths).
Workaround
Until this lands, when you've nuked something a hook produces, you have to invalidate the input-hash cache by hand:
rm -rf .dart_tool packages/*/.dart_tool examples/flutter_app/.dart_tool \
packages/flutter_scene/build examples/flutter_app/build
flutter pub get
flutter clean alone is not sufficient.
Flutter version
Flutter 3.44.0-1.0.pre-380 (channel master)
Reproducible on master.
When the per-package files produced by build hooks (e.g.
DataAssetfiles) are removed from disk,flutter runandflutter buildprintSkipping target: build_hooksand proceed to the asset-bundling step, which then fails. The hook target is treated as up-to-date even though its real outputs are gone.The cause is narrow: the existing
LinkHookstarget already declares hook-produced asset files as outputs in its depfile, but theBuildHookstarget does not. Mirroring the LinkHooks pattern in BuildHooks closes the gap, and the existingoutputMissingcheck atbuild_system/build_system.dart:1241-1246handles the rest at no additional cost.Steps to reproduce
DataAsset. Run it once successfully so the hook caches a fresh build.flutter run -d <device>.Observed (verbose):
flutter cleandoes not help because it wipesbuild/but leaves the.dart_tool/flutter_build/<hash>/native_assets.jsoncache, which still claims inputs are unchanged.Where the asymmetry is
packages/flutter_tools/lib/src/build_system/targets/native_assets.dart.LinkHooks.buildincludes hook-produced files in its depfile outputs at line 228-234:BuildHooks.builddoes not, line 79-82:So when a build hook produces a
DataAssetand the consuming app later loses the asset file, BuildHooks' cache keeps claiming up-to-date because the only output it knows about (build_hooks_result.json) is still present.Suggested fix
In
BuildHooks.build, fold the hook's bundled files into the depfile outputs, matching the LinkHooks shape. TheBuildResultfrom the hook protocol already carries the asset file URIs (the samefilesToBeBundledview LinkHooks consumes), so this is plumbing that already exists at the layer above; it just needs to be propagated into the depfile.A unit test should cover: BuildHooks runs once, declared output is deleted from disk, second run sees
outputMissingfor that path and re-runs.Note for hook authors
This fix only catches missing outputs that were registered as hook-produced assets in the first place (typically
DataAssetviaoutput.assets.addEncodedAsset(DataAsset(...).encode())). Hooks that write into the consumer'sbuild/directory and rely on the consumer's pubspecflutter: assets:block sit outside this protocol and won't benefit; those hooks should migrate to registering their outputs through the hooks API for both correctness reasons (this) and asset path consistency (packages/<pkg>/<name>instead of consumer-relative paths).Workaround
Until this lands, when you've nuked something a hook produces, you have to invalidate the input-hash cache by hand:
rm -rf .dart_tool packages/*/.dart_tool examples/flutter_app/.dart_tool \ packages/flutter_scene/build examples/flutter_app/build flutter pub getflutter cleanalone is not sufficient.Flutter version
Reproducible on master.