From fc5478961b56a70cef914b0c7c1a48d9dcc163bb Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Tue, 12 May 2026 15:13:18 +0200 Subject: [PATCH 01/12] [hooks] report build hook files to be bundled as outputs --- .../data_asset_app/hook/build.dart | 1 + .../data_asset_package/.gitignore | 3 + .../data_asset_package/hook/build.dart | 23 ++++ .../build_system/targets/native_assets.dart | 10 +- .../isolated/native_assets/native_assets.dart | 101 ++++++++++++------ .../dart_data_asset_flutter_build_test.dart | 39 +++++++ 6 files changed, 144 insertions(+), 33 deletions(-) diff --git a/dev/integration_tests/data_asset_app/hook/build.dart b/dev/integration_tests/data_asset_app/hook/build.dart index 25aa7480d17ed..2c92a7e54dff8 100644 --- a/dev/integration_tests/data_asset_app/hook/build.dart +++ b/dev/integration_tests/data_asset_app/hook/build.dart @@ -17,6 +17,7 @@ void main(List args) async { file: input.packageRoot.resolve('data/$id'), ), ); + output.dependencies.add(input.packageRoot.resolve('data/$id')); } } }); diff --git a/dev/integration_tests/data_asset_package/.gitignore b/dev/integration_tests/data_asset_package/.gitignore index dd5eb98951f29..f966f4da52b56 100644 --- a/dev/integration_tests/data_asset_package/.gitignore +++ b/dev/integration_tests/data_asset_package/.gitignore @@ -29,3 +29,6 @@ migrate_working_dir/ .flutter-plugins-dependencies /build/ /coverage/ + +# Generated test file. +data/generated.txt diff --git a/dev/integration_tests/data_asset_package/hook/build.dart b/dev/integration_tests/data_asset_package/hook/build.dart index 25aa7480d17ed..bc16610c7cd90 100644 --- a/dev/integration_tests/data_asset_package/hook/build.dart +++ b/dev/integration_tests/data_asset_package/hook/build.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:io'; + import 'package:data_assets/data_assets.dart'; import 'package:hooks/hooks.dart'; @@ -17,7 +19,28 @@ void main(List args) async { file: input.packageRoot.resolve('data/$id'), ), ); + // If the file is modified, the hook needs to be rerun. (Technically, we + // don't need to rerun because we'd output the exact same thing. But the + // hook could be doing other things based on the input file.) + output.dependencies.add(input.packageRoot.resolve('data/$id')); + } + + // Generate a data asset in the hook. + // It is better to generate to outputDirectoryShared, but users might do + // this instead and then delete the file manually. + final Uri generatedUri = input.packageRoot.resolve('data/generated.txt'); + final file = File(generatedUri.toFilePath()); + if (!file.parent.existsSync()) { + file.parent.createSync(recursive: true); } + file.writeAsStringSync('generated content'); + output.assets.data.add( + DataAsset( + package: input.packageName, + name: 'data/generated.txt', + file: generatedUri, + ), + ); } }); } diff --git a/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart b/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart index 40aacae6c3973..cbfaae0899232 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart @@ -59,6 +59,7 @@ class BuildHooks extends Target { final ( results: SerializedBuildResults results, dependencies: List dependencies, + filesToBeBundled: List filesToBeBundled, ) = await runFlutterSpecificBuildHooks( environmentDefines: environment.defines, buildRunner: buildRunner, @@ -78,7 +79,11 @@ class BuildHooks extends Target { final depfile = Depfile( [for (final Uri dependency in dependencies) fileSystem.file(dependency)], - [fileSystem.file(dartBuildOutputJsonFile)], + [ + fileSystem.file(dartBuildOutputJsonFile), + for (final Uri uri in filesToBeBundled) + if (!dependencies.contains(uri)) fileSystem.file(uri), + ], ); final File outputDepfile = environment.buildDir.childFile(depFilename); if (!outputDepfile.parent.existsSync()) { @@ -229,7 +234,8 @@ class LinkHooks extends Target { [for (final Uri dependency in result.dependencies) fileSystem.file(dependency)], [ fileSystem.file(dartHookResultJsonFile), - for (final Uri uri in result.filesToBeBundled) fileSystem.file(uri), + for (final Uri uri in result.filesToBeBundled) + if (!result.dependencies.contains(uri)) fileSystem.file(uri), ], ); final File outputDepfile = environment.buildDir.childFile(depFilename); diff --git a/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart b/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart index ab02077c618f9..a50e3f895e895 100644 --- a/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart +++ b/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart @@ -90,6 +90,7 @@ Future runFlutterSpecificHooks({ final ( results: SerializedBuildResults results, dependencies: List dependencies, + filesToBeBundled: _, ) = await runFlutterSpecificBuildHooks( environmentDefines: environmentDefines, buildRunner: buildRunner, @@ -125,7 +126,8 @@ Future runFlutterSpecificHooks({ /// /// Returns the serialized build results per target and the list of dependencies /// collected during the build stage. -Future<({SerializedBuildResults results, List dependencies})> runFlutterSpecificBuildHooks({ +Future<({SerializedBuildResults results, List dependencies, List filesToBeBundled})> +runFlutterSpecificBuildHooks({ required Map environmentDefines, required FlutterNativeAssetsBuildRunner buildRunner, required TargetPlatform targetPlatform, @@ -135,7 +137,11 @@ Future<({SerializedBuildResults results, List dependencies})> runFlutterSpe required bool buildDataAssets, }) async { if (!await _hookRunRequired(buildRunner)) { - return (results: const >{}, dependencies: const []); + return ( + results: const >{}, + dependencies: const [], + filesToBeBundled: const [], + ); } final ( @@ -158,6 +164,7 @@ Future<({SerializedBuildResults results, List dependencies})> runFlutterSpe final results = >{}; final dependencies = {}; + final filesToBeBundled = []; for (var i = 0; i < targets.length; i++) { final AssetBuildTarget target = targets[i]; // Only run non-code extensions (like data assets) for the first target, @@ -168,9 +175,18 @@ Future<({SerializedBuildResults results, List dependencies})> runFlutterSpe final BuildResult buildResult = await _build(buildRunner, extensions, linkingEnabled); results[target.targetString] = buildResult.toJson(); dependencies.addAll(buildResult.dependencies); + _decodeAssets( + encodedAssets: buildResult.encodedAssets, + target: target, + bundledFilesAccumulator: filesToBeBundled, + ); } globals.logger.printTrace('Running build hooks for $targetString done.'); - return (results: results, dependencies: dependencies.toList()); + return ( + results: results, + dependencies: dependencies.toList(), + filesToBeBundled: filesToBeBundled, + ); } List _getTargets({ @@ -301,35 +317,22 @@ Future runFlutterSpecificLinkHooks({ if (linkingEnabled) { linkResult = await _link(buildRunner, extensions, buildResult, recordedUsesFile); - if (target is CodeAssetTarget) { - codeAssets - ..addAll( - _filterCodeAssets( - linkResult.encodedAssets, - Target.fromArchitectureAndOS(target.architecture, target.os), - ), - ) - ..addAll( - _filterCodeAssets( - buildResult.encodedAssets, - Target.fromArchitectureAndOS(target.architecture, target.os), - ), - ); - } - dataAssets - ..addAll(_filterDataAssets(linkResult.encodedAssets)) - ..addAll(_filterDataAssets(buildResult.encodedAssets)); - dependencies.addAll(linkResult.dependencies); + _decodeAssets( + encodedAssets: [...linkResult.encodedAssets, ...buildResult.encodedAssets], + target: target, + codeAssetsAccumulator: codeAssets, + dataAssetsAccumulator: dataAssets, + ); + dependencies + ..addAll(buildResult.dependencies) + ..addAll(linkResult.dependencies); } else { - if (target is CodeAssetTarget) { - codeAssets.addAll( - _filterCodeAssets( - buildResult.encodedAssets, - Target.fromArchitectureAndOS(target.architecture, target.os), - ), - ); - } - dataAssets.addAll(_filterDataAssets(buildResult.encodedAssets)); + _decodeAssets( + encodedAssets: buildResult.encodedAssets, + target: target, + codeAssetsAccumulator: codeAssets, + dataAssetsAccumulator: dataAssets, + ); dependencies.addAll(buildResult.dependencies); } } @@ -357,6 +360,42 @@ Future runFlutterSpecificLinkHooks({ ); } +/// Extracts and categorizes code and data assets from [encodedAssets] for the given [target]. +/// +/// The extracted assets and files to be bundled are appended to the optional accumulator lists: +/// - [codeAssetsAccumulator]: Collects matching [FlutterCodeAsset]s. +/// - [dataAssetsAccumulator]: Collects matching [DataAsset]s. +/// - [bundledFilesAccumulator]: Collects the file URIs of dynamic libraries and data assets that need to be bundled. +void _decodeAssets({ + required Iterable encodedAssets, + required AssetBuildTarget target, + List? codeAssetsAccumulator, + List? dataAssetsAccumulator, + List? bundledFilesAccumulator, +}) { + if (target is CodeAssetTarget) { + final Iterable filteredCode = _filterCodeAssets( + encodedAssets, + Target.fromArchitectureAndOS(target.architecture, target.os), + ); + codeAssetsAccumulator?.addAll(filteredCode); + if (bundledFilesAccumulator != null) { + for (final code in filteredCode) { + if (code.codeAsset.linkMode is DynamicLoadingBundled) { + bundledFilesAccumulator.add(code.codeAsset.file!); + } + } + } + } + final Iterable filteredData = _filterDataAssets(encodedAssets); + dataAssetsAccumulator?.addAll(filteredData); + if (bundledFilesAccumulator != null) { + for (final asset in filteredData) { + bundledFilesAccumulator.add(asset.file); + } + } +} + Future> installCodeAssets({ required DartHooksResult dartHookResult, required Map environmentDefines, diff --git a/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_flutter_build_test.dart b/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_flutter_build_test.dart index 5664983cdc9ad..0ca503f8776c6 100644 --- a/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_flutter_build_test.dart +++ b/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_flutter_build_test.dart @@ -71,6 +71,45 @@ void main() { } } }); + + testWithoutContext( + 'flutter build $target rebuilds when data asset file is deleted', + () async { + final assets = {'id1.txt': 'content1'}; + writeAssets(assets, appRoot); + writeHookLibrary(appRoot, assets, available: ['id1.txt']); + writeHelperLibrary(appRoot, 'version1', assets.keys.toList()); + + ProcessTestResult result = await runFlutter( + ['build', '-v', target], + appRoot.path, + [Barrier.contains('Built build${Platform.pathSeparator}$target')], + ); + if (result.exitCode != 0) { + throw Exception( + 'first flutter build failed: ${result.exitCode}\n${result.stderr}\n${result.stdout}', + ); + } + + final File fileToDelete = dependencyRoot + .childDirectory('data') + .childFile('generated.txt'); + expect(fileToDelete.existsSync(), true); + fileToDelete.deleteSync(); + + // Second build should re-run the build_hooks target and succeed. + result = await runFlutter( + ['build', '-v', target], + appRoot.path, + [Barrier.contains('Built build${Platform.pathSeparator}$target')], + ); + if (result.exitCode != 0) { + throw Exception( + 'second flutter build failed: ${result.exitCode}\n${result.stderr}\n${result.stdout}', + ); + } + }, + ); } }); } From 9d5de5568d0fd50aee19fe2665c2ea4dbbc3c54c Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Mon, 18 May 2026 22:41:02 +0200 Subject: [PATCH 02/12] bump deps --- .../data_asset_app/pubspec.yaml | 6 +++--- .../data_asset_package/pubspec.yaml | 6 +++--- .../hook_user_defines/pubspec.yaml | 6 +++--- .../record_use_test_package/pubspec.yaml | 6 +++--- .../lib/src/update_packages_pins.dart | 2 +- packages/flutter_tools/pubspec.yaml | 10 +++++----- .../templates/package_ffi/pubspec.yaml.tmpl | 6 +++--- pubspec.lock | 20 +++++++++---------- pubspec.yaml | 19 +++++++++++++----- 9 files changed, 45 insertions(+), 36 deletions(-) diff --git a/dev/integration_tests/data_asset_app/pubspec.yaml b/dev/integration_tests/data_asset_app/pubspec.yaml index 905d6be746144..a05bb29bd5e94 100644 --- a/dev/integration_tests/data_asset_app/pubspec.yaml +++ b/dev/integration_tests/data_asset_app/pubspec.yaml @@ -12,8 +12,8 @@ resolution: workspace dependencies: flutter: sdk: flutter - hooks: ^1.0.2 - data_assets: ^0.19.6 + hooks: ^2.0.0 + data_assets: ^0.20.0 data_asset_package: path: ../data_asset_package @@ -24,4 +24,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: jl85h +# PUBSPEC CHECKSUM: 9jmf6j diff --git a/dev/integration_tests/data_asset_package/pubspec.yaml b/dev/integration_tests/data_asset_package/pubspec.yaml index 2fa645acffd85..9e4421c759c07 100644 --- a/dev/integration_tests/data_asset_package/pubspec.yaml +++ b/dev/integration_tests/data_asset_package/pubspec.yaml @@ -12,8 +12,8 @@ resolution: workspace dependencies: flutter: sdk: flutter - hooks: ^1.0.2 - data_assets: ^0.19.6 + hooks: ^2.0.0 + data_assets: ^0.20.0 dev_dependencies: flutter_test: @@ -22,4 +22,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 2g5j7i +# PUBSPEC CHECKSUM: 6cfeb0 diff --git a/dev/integration_tests/hook_user_defines/pubspec.yaml b/dev/integration_tests/hook_user_defines/pubspec.yaml index 2e4a3869b7740..b620a7dc63e14 100644 --- a/dev/integration_tests/hook_user_defines/pubspec.yaml +++ b/dev/integration_tests/hook_user_defines/pubspec.yaml @@ -15,11 +15,11 @@ hooks: magic_value: 1000 dependencies: - hooks: 1.0.3 + hooks: 2.0.0 logging: 1.3.0 - native_toolchain_c: 0.17.6 + native_toolchain_c: 0.19.0 dev_dependencies: test: 1.29.0 -# PUBSPEC CHECKSUM: 30ohqf +# PUBSPEC CHECKSUM: jmsrr2 diff --git a/dev/integration_tests/record_use_test_package/pubspec.yaml b/dev/integration_tests/record_use_test_package/pubspec.yaml index 1248355fba1c9..d1d6efbe2e9fc 100644 --- a/dev/integration_tests/record_use_test_package/pubspec.yaml +++ b/dev/integration_tests/record_use_test_package/pubspec.yaml @@ -11,8 +11,8 @@ environment: dependencies: flutter: sdk: flutter - hooks: ^1.0.2 - data_assets: ^0.19.6 + hooks: ^2.0.0 + data_assets: ^0.20.0 record_use: 0.6.0 meta: 1.18.2 @@ -23,4 +23,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 4e6oin +# PUBSPEC CHECKSUM: gl7kb7 diff --git a/packages/flutter_tools/lib/src/update_packages_pins.dart b/packages/flutter_tools/lib/src/update_packages_pins.dart index 7be7f8d7890ac..19fe93ac62ae3 100644 --- a/packages/flutter_tools/lib/src/update_packages_pins.dart +++ b/packages/flutter_tools/lib/src/update_packages_pins.dart @@ -24,7 +24,7 @@ const kManuallyPinnedDependencies = { 'flutter_gallery_assets': '1.0.2', // Tests depend on the exact version. 'flutter_template_images': '5.0.0', // Must always exactly match flutter_tools template. 'material_color_utilities': '0.13.0', // Keep pinned to latest until 1.0.0. - 'data_assets': '0.19.6', // Keep pinned to latest until 1.0.0. Rolled by @dcharkes. + 'data_assets': '0.20.0', // Keep pinned to latest until 1.0.0. Rolled by @dcharkes. 'record_use': '0.6.0', // Keep pinned to latest until 1.0.0. Rolled by @dcharkes. }; diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml index d96e4e97d7974..f828c52c208b2 100644 --- a/packages/flutter_tools/pubspec.yaml +++ b/packages/flutter_tools/pubspec.yaml @@ -58,10 +58,10 @@ dependencies: pubspec_parse: 1.5.0 graphs: 2.3.2 - hooks_runner: 1.2.1 - hooks: 1.0.3 - code_assets: 1.0.0 - data_assets: 0.19.6 + hooks_runner: 1.3.0 + hooks: 2.0.0 + code_assets: 1.1.0 + data_assets: 0.20.0 # We depend on very specific internal implementation details of the # 'test' package, which change between versions, so when upgrading @@ -128,4 +128,4 @@ dartdoc: # Exclude this package from the hosted API docs. nodoc: true -# PUBSPEC CHECKSUM: 4dafko +# PUBSPEC CHECKSUM: s3u905 diff --git a/packages/flutter_tools/templates/package_ffi/pubspec.yaml.tmpl b/packages/flutter_tools/templates/package_ffi/pubspec.yaml.tmpl index f9bb68dff1aa7..404f0e8844a0b 100644 --- a/packages/flutter_tools/templates/package_ffi/pubspec.yaml.tmpl +++ b/packages/flutter_tools/templates/package_ffi/pubspec.yaml.tmpl @@ -6,10 +6,10 @@ environment: sdk: {{dartSdkVersionBounds}} dependencies: - code_assets: ^1.0.0 - hooks: ^1.0.0 + code_assets: ^1.1.0 + hooks: ^2.0.0 logging: ^1.3.0 - native_toolchain_c: ^0.17.4 + native_toolchain_c: ^0.19.0 dev_dependencies: ffi: ^2.1.4 diff --git a/pubspec.lock b/pubspec.lock index 457e829b97ace..d747beb2ccbb6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -150,10 +150,10 @@ packages: dependency: "direct main" description: name: code_assets - sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" + sha256: dad6bf6b9f4f378b0a69edbf42584d336efd1a9ce15deb1ba591cbb1b5ff440f url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.1.0" code_builder: dependency: transitive description: @@ -255,10 +255,10 @@ packages: dependency: "direct main" description: name: data_assets - sha256: d8b93648a338f471e576e0ba05f6b5d63a4e0fa447ca839a500267421d9245ba + sha256: "8bfdbf25ec8a0f4b5a3c993042c4ab9996ba354f0b03d40f4e360f3730d71cae" url: "https://pub.dev" source: hosted - version: "0.19.6" + version: "0.20.0" dds: dependency: transitive description: @@ -502,18 +502,18 @@ packages: dependency: "direct main" description: name: hooks - sha256: "025f060e86d2d4c3c47b56e33caf7f93bf9283340f26d23424ebcfccf34f621e" + sha256: a41af4e8fc687cd6d33de9751eb936c8c0204ebe2bcb6c15ecf707504bf47f31 url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "2.0.0" hooks_runner: dependency: transitive description: name: hooks_runner - sha256: "248aaf6d687806959888be1bc0ab66bcf616c4f476e97cfc1b02cd97b8e06dd4" + sha256: b2545e50cc62b1cfcf4d94b3939a89776db72af342680d942ccad429720c4eb1 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.3.0" html: dependency: "direct main" description: @@ -718,10 +718,10 @@ packages: dependency: "direct main" description: name: native_toolchain_c - sha256: "6ba77bb18063eebe9de401f5e6437e95e1438af0a87a3a39084fbd37c90df572" + sha256: "49c147aa4a5905ec12028e2b7bfe360eace6cb91fe4cf08a3fe76e309592acae" url: "https://pub.dev" source: hosted - version: "0.17.6" + version: "0.19.0" nested: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index d0ebc3877825c..391ec3d0f1ef7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -97,7 +97,7 @@ dependencies: checked_yaml: 2.0.4 cli_config: 0.2.0 clock: ^1.1.2 - code_assets: 1.0.0 + code_assets: 1.1.0 collection: ^1.19.1 convert: 3.1.2 coverage: 1.15.0 @@ -121,8 +121,8 @@ dependencies: google_mobile_ads: 8.0.0 googleapis: 14.0.0 googleapis_auth: 2.3.1 - hooks: 1.0.3 - data_assets: 0.19.6 + hooks: 2.0.0 + data_assets: 0.20.0 record_use: 0.6.0 html: 0.15.6 http: 1.6.0 @@ -142,7 +142,7 @@ dependencies: meta: ^1.18.2 metrics_center: 1.0.15 mime: 2.0.0 - native_toolchain_c: 0.17.6 + native_toolchain_c: 0.19.0 nested: 1.0.0 node_preamble: 2.0.2 package_config: 2.2.0 @@ -224,4 +224,13 @@ dependencies: dev_dependencies: ffigen: 20.1.1 -# PUBSPEC CHECKSUM: f49okb +# Because objective_c >=9.2.3 depends on native_toolchain_c ^0.17.4 and +# objective_c >=9.2.1 <9.2.3 depends on native_toolchain_c ^0.17.2, objective_c +# >=9.2.1 requires native_toolchain_c ^0.17.2. And because +# >path_provider_foundation 2.6.0 depends on objective_c ^9.2.1, +# >path_provider_foundation 2.6.0 requires native_toolchain_c ^0.17.2. +dependency_overrides: + native_toolchain_c: 0.19.0 + hooks: 2.0.0 + +# PUBSPEC CHECKSUM: tnpcec From 91bfdc5a860c338b7d0036f221b50f2ad2e5a024 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Tue, 19 May 2026 08:30:09 +0200 Subject: [PATCH 03/12] link hook caching logic fix --- .../lib/src/build_system/targets/native_assets.dart | 6 +++++- .../lib/src/isolated/native_assets/native_assets.dart | 5 +---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart b/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart index cbfaae0899232..811d9406e004e 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart @@ -75,7 +75,11 @@ class BuildHooks extends Target { dartBuildOutputJsonFile.parent.createSync(recursive: true); } - dartBuildOutputJsonFile.writeAsStringSync(json.encode(results)); + final String encodedResults = json.encode(results); + if (!dartBuildOutputJsonFile.existsSync() || + dartBuildOutputJsonFile.readAsStringSync() != encodedResults) { + dartBuildOutputJsonFile.writeAsStringSync(encodedResults); + } final depfile = Depfile( [for (final Uri dependency in dependencies) fileSystem.file(dependency)], diff --git a/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart b/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart index a50e3f895e895..cc5a0d0698c01 100644 --- a/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart +++ b/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart @@ -323,9 +323,7 @@ Future runFlutterSpecificLinkHooks({ codeAssetsAccumulator: codeAssets, dataAssetsAccumulator: dataAssets, ); - dependencies - ..addAll(buildResult.dependencies) - ..addAll(linkResult.dependencies); + dependencies.addAll(linkResult.dependencies); } else { _decodeAssets( encodedAssets: buildResult.encodedAssets, @@ -333,7 +331,6 @@ Future runFlutterSpecificLinkHooks({ codeAssetsAccumulator: codeAssets, dataAssetsAccumulator: dataAssets, ); - dependencies.addAll(buildResult.dependencies); } } From de5556562ee1ebfcc4540a30ccc3a696e5c73d80 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Tue, 19 May 2026 09:09:45 +0200 Subject: [PATCH 04/12] [hooks] Add build dependencies to link dependencies when linking is disabled --- .../lib/src/isolated/native_assets/native_assets.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart b/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart index cc5a0d0698c01..7682c06e073d6 100644 --- a/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart +++ b/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart @@ -331,6 +331,7 @@ Future runFlutterSpecificLinkHooks({ codeAssetsAccumulator: codeAssets, dataAssetsAccumulator: dataAssets, ); + dependencies.addAll(buildResult.dependencies); } } From 28dff0e809e9214536d6cd4a0c3f8586b1945843 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Tue, 19 May 2026 09:59:39 +0200 Subject: [PATCH 05/12] another version bump --- .../isolated/dart_data_asset_conflicting_assets_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_conflicting_assets_test.dart b/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_conflicting_assets_test.dart index 9f485e15b2d77..d43efccfa3fd5 100644 --- a/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_conflicting_assets_test.dart +++ b/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_conflicting_assets_test.dart @@ -46,7 +46,7 @@ void main() { ..update(['flutter', 'assets'], [assets.keys.first]) ..update( ['dependencies'], - {'hooks': '^1.0.2', 'data_assets': '^0.19.6'}, + {'hooks': '^2.0.0', 'data_assets': '^0.20.0'}, ); }); From 21e0207fc61f6041799f949bd633dece36c205ba Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Tue, 19 May 2026 12:43:41 +0200 Subject: [PATCH 06/12] add comments about dependency tracking for build hook dependencies --- .../src/isolated/native_assets/native_assets.dart | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart b/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart index 7682c06e073d6..f1d96e86b657d 100644 --- a/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart +++ b/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart @@ -260,8 +260,7 @@ Future<({List targets, BuildMode buildMode, bool linkingEnable /// It takes the [buildResults] produced by [runFlutterSpecificBuildHooks] and /// returns a [DartHooksResult] containing the aggregated assets from both the /// build results and the link hooks. -/// -/// The returned dependencies only include those collected during the link stage. +/// The returned dependencies include both the build-stage and link-stage dependencies. Future runFlutterSpecificLinkHooks({ required Map environmentDefines, required FlutterNativeAssetsBuildRunner buildRunner, @@ -331,8 +330,18 @@ Future runFlutterSpecificLinkHooks({ codeAssetsAccumulator: codeAssets, dataAssetsAccumulator: dataAssets, ); - dependencies.addAll(buildResult.dependencies); } + + // We must add all build-stage dependencies to the aggregated dependencies. + // This serves two purposes: + // 1. It ensures files that are passed through from the build stage (which are in + // filesToBeBundled but not generated by the link stage) are not incorrectly + // categorized as output files rather than input files by the depfile service. + // If they are omitted here, they are treated as outputs of the link stage, + // resulting in circular dependencies in Xcode's build graph. + // 2. It ensures that modifications to build-stage source files correctly trigger + // rebuilding/re-evaluation of the link-stage target by the build system's caching. + dependencies.addAll(buildResult.dependencies); } if (dataAssets.map((DataAsset asset) => asset.id).toSet().length != dataAssets.length) { From 67c7fc6405e8c9528a6a378ba6e768bb9dbc46ac Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Tue, 19 May 2026 13:49:10 +0200 Subject: [PATCH 07/12] try removing BuildHooks DEPS from LinkHooks --- .../build_system/targets/native_assets.dart | 32 +++-- .../isolated/native_assets/native_assets.dart | 132 +++++++++++++----- 2 files changed, 123 insertions(+), 41 deletions(-) diff --git a/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart b/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart index 811d9406e004e..f2f82fe73308e 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart @@ -216,30 +216,46 @@ class LinkHooks extends Target { final Map> buildResults = serializedBuildResults .cast>(); - final DartHooksResult result = await runFlutterSpecificLinkHooks( + final linkingEnabled = buildMode != BuildMode.debug; + final DartHooksResult result; + if (linkingEnabled) { + result = await runFlutterSpecificLinkHooks( + environmentDefines: environment.defines, + buildRunner: buildRunner, + targetPlatform: targetPlatform, + projectUri: projectUri, + fileSystem: fileSystem, + buildCodeAssets: BuildCodeAssetsOptions(appBuildDirectory: environment.outputDir), + buildDataAssets: true, + buildResults: buildResults, + recordedUsesFile: recordedUsesFileToPass, + ); + } else { + result = DartHooksResult.empty(); + } + + final DartHooksResult combinedResult = combineBuildAndLinkResults( environmentDefines: environment.defines, - buildRunner: buildRunner, targetPlatform: targetPlatform, - projectUri: projectUri, fileSystem: fileSystem, buildCodeAssets: BuildCodeAssetsOptions(appBuildDirectory: environment.outputDir), buildDataAssets: true, buildResults: buildResults, - recordedUsesFile: recordedUsesFileToPass, + linkResult: result, ); final File dartHookResultJsonFile = environment.buildDir.childFile(resultFilename); if (!dartHookResultJsonFile.parent.existsSync()) { dartHookResultJsonFile.parent.createSync(recursive: true); } - dartHookResultJsonFile.writeAsStringSync(json.encode(result.toJson())); - + dartHookResultJsonFile.writeAsStringSync(json.encode(combinedResult.toJson())); final depfile = Depfile( [for (final Uri dependency in result.dependencies) fileSystem.file(dependency)], [ fileSystem.file(dartHookResultJsonFile), - for (final Uri uri in result.filesToBeBundled) - if (!result.dependencies.contains(uri)) fileSystem.file(uri), + if (linkingEnabled) + for (final Uri uri in result.filesToBeBundled) + if (!result.dependencies.contains(uri)) fileSystem.file(uri), ], ); final File outputDepfile = environment.buildDir.childFile(depFilename); diff --git a/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart b/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart index f1d96e86b657d..2ecbc76bf0592 100644 --- a/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart +++ b/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart @@ -101,24 +101,44 @@ Future runFlutterSpecificHooks({ buildDataAssets: buildDataAssets, ); - final DartHooksResult linkResult = await runFlutterSpecificLinkHooks( + final BuildMode buildMode = _getBuildMode( + environmentDefines, + targetPlatform == TargetPlatform.tester, + ); + final bool linkingEnabled = _nativeAssetsLinkingEnabled(buildMode); + final DartHooksResult linkResult; + if (linkingEnabled) { + linkResult = await runFlutterSpecificLinkHooks( + environmentDefines: environmentDefines, + buildRunner: buildRunner, + targetPlatform: targetPlatform, + projectUri: projectUri, + fileSystem: fileSystem, + buildCodeAssets: buildCodeAssets, + buildDataAssets: buildDataAssets, + buildResults: results, + recordedUsesFile: recordedUsesFile, + ); + } else { + linkResult = DartHooksResult.empty(); + } + + final DartHooksResult combinedResult = combineBuildAndLinkResults( environmentDefines: environmentDefines, - buildRunner: buildRunner, targetPlatform: targetPlatform, - projectUri: projectUri, fileSystem: fileSystem, buildCodeAssets: buildCodeAssets, buildDataAssets: buildDataAssets, buildResults: results, - recordedUsesFile: recordedUsesFile, + linkResult: linkResult, ); return DartHooksResult( buildStart: buildStart, - buildEnd: linkResult.buildEnd, - codeAssets: linkResult.codeAssets, - dataAssets: linkResult.dataAssets, - dependencies: {...dependencies, ...linkResult.dependencies}.toList(), + buildEnd: combinedResult.buildEnd, + codeAssets: combinedResult.codeAssets, + dataAssets: combinedResult.dataAssets, + dependencies: combinedResult.dependencies, ); } @@ -257,10 +277,8 @@ Future<({List targets, BuildMode buildMode, bool linkingEnable /// Invokes the link hooks of all transitive Dart package hooks. /// -/// It takes the [buildResults] produced by [runFlutterSpecificBuildHooks] and -/// returns a [DartHooksResult] containing the aggregated assets from both the -/// build results and the link hooks. -/// The returned dependencies include both the build-stage and link-stage dependencies. +/// The returned dependencies only include those collected during the link stage. +/// The returned assets only include those produced during the link stage. Future runFlutterSpecificLinkHooks({ required Map environmentDefines, required FlutterNativeAssetsBuildRunner buildRunner, @@ -312,36 +330,22 @@ Future runFlutterSpecificLinkHooks({ } final buildResult = BuildResult.fromJson(buildResultJson); - LinkResult? linkResult; if (linkingEnabled) { - linkResult = await _link(buildRunner, extensions, buildResult, recordedUsesFile); + final LinkResult linkResult = await _link( + buildRunner, + extensions, + buildResult, + recordedUsesFile, + ); _decodeAssets( - encodedAssets: [...linkResult.encodedAssets, ...buildResult.encodedAssets], + encodedAssets: linkResult.encodedAssets, target: target, codeAssetsAccumulator: codeAssets, dataAssetsAccumulator: dataAssets, ); dependencies.addAll(linkResult.dependencies); - } else { - _decodeAssets( - encodedAssets: buildResult.encodedAssets, - target: target, - codeAssetsAccumulator: codeAssets, - dataAssetsAccumulator: dataAssets, - ); } - - // We must add all build-stage dependencies to the aggregated dependencies. - // This serves two purposes: - // 1. It ensures files that are passed through from the build stage (which are in - // filesToBeBundled but not generated by the link stage) are not incorrectly - // categorized as output files rather than input files by the depfile service. - // If they are omitted here, they are treated as outputs of the link stage, - // resulting in circular dependencies in Xcode's build graph. - // 2. It ensures that modifications to build-stage source files correctly trigger - // rebuilding/re-evaluation of the link-stage target by the build system's caching. - dependencies.addAll(buildResult.dependencies); } if (dataAssets.map((DataAsset asset) => asset.id).toSet().length != dataAssets.length) { @@ -367,6 +371,68 @@ Future runFlutterSpecificLinkHooks({ ); } +/// Combines build-stage and link-stage results into a single, combined [DartHooksResult]. +/// +/// The combined result contains all code and data assets from both stages, +/// and the union of all dependencies from both stages. +DartHooksResult combineBuildAndLinkResults({ + required Map environmentDefines, + required TargetPlatform targetPlatform, + required FileSystem fileSystem, + required BuildCodeAssetsOptions? buildCodeAssets, + required bool buildDataAssets, + required SerializedBuildResults buildResults, + required DartHooksResult linkResult, +}) { + final List targets = _getTargets( + environmentDefines: environmentDefines, + targetPlatform: targetPlatform, + fileSystem: fileSystem, + buildCodeAssets: buildCodeAssets, + buildDataAssets: buildDataAssets, + ); + + final codeAssets = [...linkResult.codeAssets]; + final dataAssets = [...linkResult.dataAssets]; + final dependencies = {...linkResult.dependencies}; + + for (var i = 0; i < targets.length; i++) { + final AssetBuildTarget target = targets[i]; + final Map? buildResultJson = buildResults[target.targetString]; + if (buildResultJson == null) { + continue; + } + final buildResult = BuildResult.fromJson(buildResultJson); + _decodeAssets( + encodedAssets: buildResult.encodedAssets, + target: target, + codeAssetsAccumulator: codeAssets, + dataAssetsAccumulator: dataAssets, + ); + dependencies.addAll(buildResult.dependencies); + } + + if (dataAssets.map((DataAsset asset) => asset.id).toSet().length != dataAssets.length) { + throwToolExit( + 'Found duplicates in the data assets: ${dataAssets.map((DataAsset e) => e.id).toList()} while compiling for ${targets.map((AssetBuildTarget e) => e.targetString).toList()}.', + ); + } + + if (codeAssets.toSet().length != codeAssets.length) { + throwToolExit( + 'Found duplicates in the code assets: ${codeAssets.map((FlutterCodeAsset e) => e.codeAsset.id).toList()} while compiling for ${targets.map((AssetBuildTarget e) => e.targetString).toList()}.', + ); + } + + return DartHooksResult( + buildStart: linkResult.buildStart, + buildEnd: linkResult.buildEnd, + codeAssets: codeAssets, + dataAssets: dataAssets, + dependencies: dependencies.toList(), + ); +} + /// Extracts and categorizes code and data assets from [encodedAssets] for the given [target]. /// /// The extracted assets and files to be bundled are appended to the optional accumulator lists: From 2f51a4345c9c35896f6292e2bdf7f2b23703def8 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Tue, 19 May 2026 17:32:46 +0200 Subject: [PATCH 08/12] cleanup --- .../targets/hook_runner_native.dart | 8 +++-- .../build_system/targets/native_assets.dart | 34 +++++++++---------- .../isolated/native_assets/native_assets.dart | 29 +++++++++------- 3 files changed, 39 insertions(+), 32 deletions(-) diff --git a/packages/flutter_tools/lib/src/build_system/targets/hook_runner_native.dart b/packages/flutter_tools/lib/src/build_system/targets/hook_runner_native.dart index e3e835b7b89f3..89bceb69b518c 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/hook_runner_native.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/hook_runner_native.dart @@ -37,7 +37,10 @@ class FlutterHookRunnerNative implements FlutterHookRunner { environment, ); - final DartHooksResult dartHooksResult = await runFlutterSpecificHooks( + final ( + results: _, + buildResult: DartHooksResult buildResult, + ) = await runFlutterSpecificBuildHooks( environmentDefines: environment.defines, buildRunner: buildRunner, targetPlatform: targetPlatform, @@ -45,10 +48,9 @@ class FlutterHookRunnerNative implements FlutterHookRunner { fileSystem: environment.fileSystem, buildCodeAssets: null, buildDataAssets: true, - recordedUsesFile: null, ); - final FlutterHookResult flutterHookResult = dartHooksResult.asFlutterResult; + final FlutterHookResult flutterHookResult = buildResult.asFlutterResult; _flutterHookResult = flutterHookResult; logger?.printTrace('runHooks() - done'); return flutterHookResult; diff --git a/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart b/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart index f2f82fe73308e..e92764e4944ca 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart @@ -58,8 +58,7 @@ class BuildHooks extends Target { _buildRunner ?? await createFlutterNativeAssetsBuildRunner(environment); final ( results: SerializedBuildResults results, - dependencies: List dependencies, - filesToBeBundled: List filesToBeBundled, + buildResult: DartHooksResult buildResult, ) = await runFlutterSpecificBuildHooks( environmentDefines: environment.defines, buildRunner: buildRunner, @@ -82,11 +81,11 @@ class BuildHooks extends Target { } final depfile = Depfile( - [for (final Uri dependency in dependencies) fileSystem.file(dependency)], + [for (final Uri dependency in buildResult.dependencies) fileSystem.file(dependency)], [ fileSystem.file(dartBuildOutputJsonFile), - for (final Uri uri in filesToBeBundled) - if (!dependencies.contains(uri)) fileSystem.file(uri), + for (final Uri uri in buildResult.filesToBeBundled) + if (!buildResult.dependencies.contains(uri)) fileSystem.file(uri), ], ); final File outputDepfile = environment.buildDir.childFile(depFilename); @@ -206,6 +205,8 @@ class LinkHooks extends Target { final buildMode = BuildMode.fromCliName(buildModeEnvironment); final File? recordedUsesFileToPass = getRecordedUsesFile(environment, buildMode); + final linkingEnabled = buildMode != BuildMode.debug; + // Read the result of BuildHooks. final File dartBuildOutputJsonFile = environment.buildDir.childFile(BuildHooks.resultFilename); if (!dartBuildOutputJsonFile.existsSync()) { @@ -216,10 +217,9 @@ class LinkHooks extends Target { final Map> buildResults = serializedBuildResults .cast>(); - final linkingEnabled = buildMode != BuildMode.debug; - final DartHooksResult result; + final DartHooksResult linkResult; if (linkingEnabled) { - result = await runFlutterSpecificLinkHooks( + linkResult = await runFlutterSpecificLinkHooks( environmentDefines: environment.defines, buildRunner: buildRunner, targetPlatform: targetPlatform, @@ -231,7 +231,7 @@ class LinkHooks extends Target { recordedUsesFile: recordedUsesFileToPass, ); } else { - result = DartHooksResult.empty(); + linkResult = DartHooksResult.empty(); } final DartHooksResult combinedResult = combineBuildAndLinkResults( @@ -241,7 +241,7 @@ class LinkHooks extends Target { buildCodeAssets: BuildCodeAssetsOptions(appBuildDirectory: environment.outputDir), buildDataAssets: true, buildResults: buildResults, - linkResult: result, + linkResult: linkResult, ); final File dartHookResultJsonFile = environment.buildDir.childFile(resultFilename); @@ -250,12 +250,12 @@ class LinkHooks extends Target { } dartHookResultJsonFile.writeAsStringSync(json.encode(combinedResult.toJson())); final depfile = Depfile( - [for (final Uri dependency in result.dependencies) fileSystem.file(dependency)], + [for (final Uri dependency in linkResult.dependencies) fileSystem.file(dependency)], [ fileSystem.file(dartHookResultJsonFile), if (linkingEnabled) - for (final Uri uri in result.filesToBeBundled) - if (!result.dependencies.contains(uri)) fileSystem.file(uri), + for (final Uri uri in linkResult.filesToBeBundled) + if (!linkResult.dependencies.contains(uri)) fileSystem.file(uri), ], ); final File outputDepfile = environment.buildDir.childFile(depFilename); @@ -304,8 +304,8 @@ class InstallCodeAssets extends Target { final FileSystem fileSystem = environment.fileSystem; final TargetPlatform targetPlatform = _getTargetPlatformFromEnvironment(environment, name); - // We fetch the result from the [LinkHooks]. - final DartHooksResult dartHookResult = await LinkHooks.loadHookResult(environment); + // We fetch the combined result from the [LinkHooks]. + final DartHooksResult combinedResult = await LinkHooks.loadHookResult(environment); // And install/copy the code assets to the right place and create a // native_asset.yaml that can be used by the final AOT compilation. @@ -319,7 +319,7 @@ class InstallCodeAssets extends Target { } final List installedFiles = await installCodeAssets( - dartHookResult: dartHookResult, + dartHookResult: combinedResult, environmentDefines: environment.defines, targetPlatform: targetPlatform, projectUri: projectUri, @@ -330,7 +330,7 @@ class InstallCodeAssets extends Target { assert(fileSystem.file(nativeAssetsFileUri).existsSync()); final depfile = Depfile([ - for (final Uri file in dartHookResult.filesToBeBundled) fileSystem.file(file), + for (final Uri file in combinedResult.filesToBeBundled) fileSystem.file(file), ], installedFiles); final File outputDepfile = environment.buildDir.childFile(depFilename); environment.depFileService.writeToFile(depfile, outputDepfile); diff --git a/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart b/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart index 2ecbc76bf0592..e3d50a9629d5c 100644 --- a/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart +++ b/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart @@ -89,8 +89,7 @@ Future runFlutterSpecificHooks({ final ( results: SerializedBuildResults results, - dependencies: List dependencies, - filesToBeBundled: _, + buildResult: DartHooksResult _, ) = await runFlutterSpecificBuildHooks( environmentDefines: environmentDefines, buildRunner: buildRunner, @@ -146,7 +145,7 @@ Future runFlutterSpecificHooks({ /// /// Returns the serialized build results per target and the list of dependencies /// collected during the build stage. -Future<({SerializedBuildResults results, List dependencies, List filesToBeBundled})> +Future<({SerializedBuildResults results, DartHooksResult buildResult})> runFlutterSpecificBuildHooks({ required Map environmentDefines, required FlutterNativeAssetsBuildRunner buildRunner, @@ -157,13 +156,11 @@ runFlutterSpecificBuildHooks({ required bool buildDataAssets, }) async { if (!await _hookRunRequired(buildRunner)) { - return ( - results: const >{}, - dependencies: const [], - filesToBeBundled: const [], - ); + return (results: const >{}, buildResult: DartHooksResult.empty()); } + final buildStart = DateTime.now(); + final ( targets: List targets, buildMode: BuildMode buildMode, @@ -184,7 +181,9 @@ runFlutterSpecificBuildHooks({ final results = >{}; final dependencies = {}; - final filesToBeBundled = []; + final codeAssets = []; + final dataAssets = []; + for (var i = 0; i < targets.length; i++) { final AssetBuildTarget target = targets[i]; // Only run non-code extensions (like data assets) for the first target, @@ -198,14 +197,20 @@ runFlutterSpecificBuildHooks({ _decodeAssets( encodedAssets: buildResult.encodedAssets, target: target, - bundledFilesAccumulator: filesToBeBundled, + codeAssetsAccumulator: codeAssets, + dataAssetsAccumulator: dataAssets, ); } globals.logger.printTrace('Running build hooks for $targetString done.'); return ( results: results, - dependencies: dependencies.toList(), - filesToBeBundled: filesToBeBundled, + buildResult: DartHooksResult( + buildStart: buildStart, + buildEnd: DateTime.now(), + codeAssets: codeAssets, + dataAssets: dataAssets, + dependencies: dependencies.toList(), + ), ); } From 6c5d0e91616ede62a8e222ceba7be222babdae62 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Tue, 19 May 2026 17:36:25 +0200 Subject: [PATCH 09/12] Trigger Build From 22fd24d2b53dbac0827a22ecaf9cc8e60f2f9939 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Wed, 20 May 2026 08:53:12 +0200 Subject: [PATCH 10/12] address comments --- .../build_system/targets/native_assets.dart | 13 +++- .../isolated/native_assets/native_assets.dart | 69 +++++++++---------- .../isolated/native_assets_test.dart | 38 ++++++++++ 3 files changed, 82 insertions(+), 38 deletions(-) diff --git a/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart b/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart index e92764e4944ca..366999f657098 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart @@ -80,12 +80,13 @@ class BuildHooks extends Target { dartBuildOutputJsonFile.writeAsStringSync(encodedResults); } + final Set buildDependencies = buildResult.dependencies.toSet(); final depfile = Depfile( [for (final Uri dependency in buildResult.dependencies) fileSystem.file(dependency)], [ fileSystem.file(dartBuildOutputJsonFile), for (final Uri uri in buildResult.filesToBeBundled) - if (!buildResult.dependencies.contains(uri)) fileSystem.file(uri), + if (!buildDependencies.contains(uri)) fileSystem.file(uri), ], ); final File outputDepfile = environment.buildDir.childFile(depFilename); @@ -106,7 +107,6 @@ class BuildHooks extends Target { // If different packages are resolved, different native assets might need to // be built. Source.pattern('{WORKSPACE_DIR}/.dart_tool/package_config.json'), - // TODO(mosuem): Should consume resources.json. https://github.com/flutter/flutter/issues/146263 ]; @override @@ -248,14 +248,21 @@ class LinkHooks extends Target { if (!dartHookResultJsonFile.parent.existsSync()) { dartHookResultJsonFile.parent.createSync(recursive: true); } + // TODO(dcharkes): The build system uses file hashing to determine if + // targets need to be rerun. Because combinedResult.toJson() includes + // transient build_start and build_end times, this file is rewritten on + // every build causing downstream targets to rerun. We should remove + // build_start and build_end from the JSON representation entirely in a + // future PR. dartHookResultJsonFile.writeAsStringSync(json.encode(combinedResult.toJson())); + final Set linkDependencies = linkResult.dependencies.toSet(); final depfile = Depfile( [for (final Uri dependency in linkResult.dependencies) fileSystem.file(dependency)], [ fileSystem.file(dartHookResultJsonFile), if (linkingEnabled) for (final Uri uri in linkResult.filesToBeBundled) - if (!linkResult.dependencies.contains(uri)) fileSystem.file(uri), + if (!linkDependencies.contains(uri)) fileSystem.file(uri), ], ); final File outputDepfile = environment.buildDir.childFile(depFilename); diff --git a/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart b/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart index e3d50a9629d5c..7a7838fd52c62 100644 --- a/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart +++ b/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart @@ -201,6 +201,7 @@ runFlutterSpecificBuildHooks({ dataAssetsAccumulator: dataAssets, ); } + _checkForDuplicateAssets(codeAssets: codeAssets, dataAssets: dataAssets, targets: targets); globals.logger.printTrace('Running build hooks for $targetString done.'); return ( results: results, @@ -353,17 +354,7 @@ Future runFlutterSpecificLinkHooks({ } } - if (dataAssets.map((DataAsset asset) => asset.id).toSet().length != dataAssets.length) { - throwToolExit( - 'Found duplicates in the data assets: ${dataAssets.map((DataAsset e) => e.id).toList()} while compiling for ${targets.map((AssetBuildTarget e) => e.targetString).toList()}.', - ); - } - - if (codeAssets.toSet().length != codeAssets.length) { - throwToolExit( - 'Found duplicates in the code assets: ${codeAssets.map((FlutterCodeAsset e) => e.codeAsset.id).toList()} while compiling for ${targets.map((AssetBuildTarget e) => e.targetString).toList()}.', - ); - } + _checkForDuplicateAssets(codeAssets: codeAssets, dataAssets: dataAssets, targets: targets); globals.logger.printTrace('Running link hooks for $targetString done.'); @@ -417,17 +408,7 @@ DartHooksResult combineBuildAndLinkResults({ dependencies.addAll(buildResult.dependencies); } - if (dataAssets.map((DataAsset asset) => asset.id).toSet().length != dataAssets.length) { - throwToolExit( - 'Found duplicates in the data assets: ${dataAssets.map((DataAsset e) => e.id).toList()} while compiling for ${targets.map((AssetBuildTarget e) => e.targetString).toList()}.', - ); - } - - if (codeAssets.toSet().length != codeAssets.length) { - throwToolExit( - 'Found duplicates in the code assets: ${codeAssets.map((FlutterCodeAsset e) => e.codeAsset.id).toList()} while compiling for ${targets.map((AssetBuildTarget e) => e.targetString).toList()}.', - ); - } + _checkForDuplicateAssets(codeAssets: codeAssets, dataAssets: dataAssets, targets: targets); return DartHooksResult( buildStart: linkResult.buildStart, @@ -440,16 +421,14 @@ DartHooksResult combineBuildAndLinkResults({ /// Extracts and categorizes code and data assets from [encodedAssets] for the given [target]. /// -/// The extracted assets and files to be bundled are appended to the optional accumulator lists: +/// The extracted assets are appended to the optional accumulator lists: /// - [codeAssetsAccumulator]: Collects matching [FlutterCodeAsset]s. /// - [dataAssetsAccumulator]: Collects matching [DataAsset]s. -/// - [bundledFilesAccumulator]: Collects the file URIs of dynamic libraries and data assets that need to be bundled. void _decodeAssets({ required Iterable encodedAssets, required AssetBuildTarget target, List? codeAssetsAccumulator, List? dataAssetsAccumulator, - List? bundledFilesAccumulator, }) { if (target is CodeAssetTarget) { final Iterable filteredCode = _filterCodeAssets( @@ -457,21 +436,41 @@ void _decodeAssets({ Target.fromArchitectureAndOS(target.architecture, target.os), ); codeAssetsAccumulator?.addAll(filteredCode); - if (bundledFilesAccumulator != null) { - for (final code in filteredCode) { - if (code.codeAsset.linkMode is DynamicLoadingBundled) { - bundledFilesAccumulator.add(code.codeAsset.file!); - } - } - } } final Iterable filteredData = _filterDataAssets(encodedAssets); dataAssetsAccumulator?.addAll(filteredData); - if (bundledFilesAccumulator != null) { - for (final asset in filteredData) { - bundledFilesAccumulator.add(asset.file); +} + +void _checkForDuplicateAssets({ + required List codeAssets, + required List dataAssets, + required List targets, +}) { + final dataAssetIds = {}; + final duplicateDataAssetIds = {}; + for (final asset in dataAssets) { + if (!dataAssetIds.add(asset.id)) { + duplicateDataAssetIds.add(asset.id); + } + } + if (duplicateDataAssetIds.isNotEmpty) { + throwToolExit( + 'Found duplicates in the data assets: ${duplicateDataAssetIds.toList()} while compiling for ${targets.map((AssetBuildTarget e) => e.targetString).toList()}.', + ); + } + + final codeAssetIds = <(String, Target)>{}; + final duplicateCodeAssetIds = {}; + for (final asset in codeAssets) { + if (!codeAssetIds.add((asset.codeAsset.id, asset.target))) { + duplicateCodeAssetIds.add(asset.codeAsset.id); } } + if (duplicateCodeAssetIds.isNotEmpty) { + throwToolExit( + 'Found duplicates in the code assets: ${duplicateCodeAssetIds.toList()} while compiling for ${targets.map((AssetBuildTarget e) => e.targetString).toList()}.', + ); + } } Future> installCodeAssets({ diff --git a/packages/flutter_tools/test/general.shard/isolated/native_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/native_assets_test.dart index 9b1795cc1eb20..6926e7ece5988 100644 --- a/packages/flutter_tools/test/general.shard/isolated/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/native_assets_test.dart @@ -250,6 +250,44 @@ void main() { }, ); + testUsingContext( + 'Native assets: duplicate assets throws tool exit listing duplicate IDs', + overrides: {ProcessManager: () => FakeProcessManager.empty()}, + () async { + final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + + final File directSoFile = environment.projectDir.childFile('direct.so'); + directSoFile.writeAsBytesSync([]); + + CodeAsset makeCodeAsset(String name, Uri file, LinkMode linkMode) => + CodeAsset(package: 'bar', name: name, linkMode: linkMode, file: file); + + expect( + () => runFlutterSpecificHooks( + environmentDefines: {kBuildMode: BuildMode.release.cliName}, + targetPlatform: TargetPlatform.linux_x64, + projectUri: projectUri, + fileSystem: fileSystem, + buildRunner: FakeFlutterNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: ['bar'], + buildResult: FakeFlutterNativeAssetsBuilderResult.fromAssets( + codeAssets: [ + makeCodeAsset('direct', directSoFile.uri, DynamicLoadingBundled()), + makeCodeAsset('direct', directSoFile.uri, DynamicLoadingBundled()), + ], + ), + ), + buildCodeAssets: const BuildCodeAssetsOptions(appBuildDirectory: null), + buildDataAssets: true, + recordedUsesFile: null, + ), + throwsToolExit(message: 'Found duplicates in the code assets: [package:bar/direct]'), + ); + }, + ); + testUsingContext( 'unit tests does not require compiler toolchain', overrides: { From 08cd19aa4c0f81adcb152fbc59c8349f43af53f2 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Wed, 20 May 2026 09:40:40 +0200 Subject: [PATCH 11/12] fix tests --- .../isolated/android/native_assets_test.dart | 10 ++++++++-- .../isolated/windows/native_assets_test.dart | 6 +++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/flutter_tools/test/general.shard/isolated/android/native_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/android/native_assets_test.dart index 0b973fe35ef27..879276cddb958 100644 --- a/packages/flutter_tools/test/general.shard/isolated/android/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/android/native_assets_test.dart @@ -78,8 +78,14 @@ void main() { ]; final buildRunner = FakeFlutterNativeAssetsBuildRunner( packagesWithNativeAssetsResult: ['bar'], - buildResult: FakeFlutterNativeAssetsBuilderResult.fromAssets(codeAssets: codeAssets), - linkResult: FakeFlutterNativeAssetsBuilderResult.fromAssets(codeAssets: codeAssets), + buildResult: buildMode == BuildMode.debug + ? FakeFlutterNativeAssetsBuilderResult.fromAssets(codeAssets: codeAssets) + : FakeFlutterNativeAssetsBuilderResult.fromAssets( + codeAssetsForLinking: >{'package:bar': codeAssets}, + ), + linkResult: buildMode == BuildMode.debug + ? null + : FakeFlutterNativeAssetsBuilderResult.fromAssets(codeAssets: codeAssets), ); final environmentDefines = { kBuildMode: buildMode.cliName, diff --git a/packages/flutter_tools/test/general.shard/isolated/windows/native_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/windows/native_assets_test.dart index 22b5ba23cb168..2a9b8e5dc2f94 100644 --- a/packages/flutter_tools/test/general.shard/isolated/windows/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/windows/native_assets_test.dart @@ -86,7 +86,11 @@ void main() { ]; final buildRunner = FakeFlutterNativeAssetsBuildRunner( packagesWithNativeAssetsResult: ['bar'], - buildResult: FakeFlutterNativeAssetsBuilderResult.fromAssets(codeAssets: codeAssets), + buildResult: buildMode == BuildMode.debug + ? FakeFlutterNativeAssetsBuilderResult.fromAssets(codeAssets: codeAssets) + : FakeFlutterNativeAssetsBuilderResult.fromAssets( + codeAssetsForLinking: >{'package:bar': codeAssets}, + ), linkResult: buildMode == BuildMode.debug ? null : FakeFlutterNativeAssetsBuilderResult.fromAssets(codeAssets: codeAssets), From b5309dc1dcf68c1719c095d625e0c87228ac2b30 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Fri, 22 May 2026 20:10:00 +0200 Subject: [PATCH 12/12] address comments --- .../targets/hook_runner_native.dart | 5 +---- .../build_system/targets/native_assets.dart | 4 ++-- .../isolated/native_assets/native_assets.dart | 18 ++++++++++-------- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/packages/flutter_tools/lib/src/build_system/targets/hook_runner_native.dart b/packages/flutter_tools/lib/src/build_system/targets/hook_runner_native.dart index 89bceb69b518c..eb8c192dd453b 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/hook_runner_native.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/hook_runner_native.dart @@ -37,10 +37,7 @@ class FlutterHookRunnerNative implements FlutterHookRunner { environment, ); - final ( - results: _, - buildResult: DartHooksResult buildResult, - ) = await runFlutterSpecificBuildHooks( + final (:DartHooksResult buildResult, results: _) = await runFlutterSpecificBuildHooks( environmentDefines: environment.defines, buildRunner: buildRunner, targetPlatform: targetPlatform, diff --git a/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart b/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart index 366999f657098..3a3fb42e3190b 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart @@ -57,8 +57,8 @@ class BuildHooks extends Target { final FlutterNativeAssetsBuildRunner buildRunner = _buildRunner ?? await createFlutterNativeAssetsBuildRunner(environment); final ( - results: SerializedBuildResults results, - buildResult: DartHooksResult buildResult, + :SerializedBuildResults results, + :DartHooksResult buildResult, ) = await runFlutterSpecificBuildHooks( environmentDefines: environment.defines, buildRunner: buildRunner, diff --git a/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart b/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart index 7a7838fd52c62..3e0ec38639659 100644 --- a/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart +++ b/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart @@ -87,10 +87,7 @@ Future runFlutterSpecificHooks({ final buildStart = DateTime.now(); - final ( - results: SerializedBuildResults results, - buildResult: DartHooksResult _, - ) = await runFlutterSpecificBuildHooks( + final (:SerializedBuildResults results, buildResult: _) = await runFlutterSpecificBuildHooks( environmentDefines: environmentDefines, buildRunner: buildRunner, targetPlatform: targetPlatform, @@ -392,8 +389,7 @@ DartHooksResult combineBuildAndLinkResults({ final dataAssets = [...linkResult.dataAssets]; final dependencies = {...linkResult.dependencies}; - for (var i = 0; i < targets.length; i++) { - final AssetBuildTarget target = targets[i]; + for (final target in targets) { final Map? buildResultJson = buildResults[target.targetString]; if (buildResultJson == null) { continue; @@ -446,6 +442,8 @@ void _checkForDuplicateAssets({ required List dataAssets, required List targets, }) { + final List targetStrings = targets.map((AssetBuildTarget e) => e.targetString).toList(); + final dataAssetIds = {}; final duplicateDataAssetIds = {}; for (final asset in dataAssets) { @@ -455,7 +453,9 @@ void _checkForDuplicateAssets({ } if (duplicateDataAssetIds.isNotEmpty) { throwToolExit( - 'Found duplicates in the data assets: ${duplicateDataAssetIds.toList()} while compiling for ${targets.map((AssetBuildTarget e) => e.targetString).toList()}.', + 'Found duplicates in the data assets: ' + '${duplicateDataAssetIds.toList()} while compiling for ' + '$targetStrings.', ); } @@ -468,7 +468,9 @@ void _checkForDuplicateAssets({ } if (duplicateCodeAssetIds.isNotEmpty) { throwToolExit( - 'Found duplicates in the code assets: ${duplicateCodeAssetIds.toList()} while compiling for ${targets.map((AssetBuildTarget e) => e.targetString).toList()}.', + 'Found duplicates in the code assets: ' + '${duplicateCodeAssetIds.toList()} while compiling for ' + '$targetStrings.', ); } }