diff --git a/DEPS b/DEPS
index acb41ff28fa7a..02939d351f16c 100644
--- a/DEPS
+++ b/DEPS
@@ -56,7 +56,7 @@ vars = {
# Dart is: https://github.com/dart-lang/sdk/blob/main/DEPS
# You can use //tools/dart/create_updated_flutter_deps.py to produce
# updated revision list of existing dependencies.
- 'dart_revision': 'd70b123c77d4115f332652c9bb21ac46a2456889',
+ 'dart_revision': '05fa9dd6af23dd3f74fd402f8ba917fe7180112a',
# WARNING: DO NOT EDIT MANUALLY
# The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py
diff --git a/dev/icon_treeshaker/.gitignore b/dev/icon_treeshaker/.gitignore
new file mode 100644
index 0000000000000..3cceda5578968
--- /dev/null
+++ b/dev/icon_treeshaker/.gitignore
@@ -0,0 +1,7 @@
+# https://dart.dev/guides/libraries/private-files
+# Created by `dart pub`
+.dart_tool/
+
+# Avoid committing pubspec.lock for library packages; see
+# https://dart.dev/guides/libraries/private-files#pubspeclock.
+pubspec.lock
diff --git a/dev/icon_treeshaker/CHANGELOG.md b/dev/icon_treeshaker/CHANGELOG.md
new file mode 100644
index 0000000000000..effe43c82c8a7
--- /dev/null
+++ b/dev/icon_treeshaker/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 1.0.0
+
+- Initial version.
diff --git a/dev/icon_treeshaker/README.md b/dev/icon_treeshaker/README.md
new file mode 100644
index 0000000000000..8831761b897a1
--- /dev/null
+++ b/dev/icon_treeshaker/README.md
@@ -0,0 +1,39 @@
+
+
+TODO: Put a short description of the package here that helps potential users
+know whether this package might be useful for them.
+
+## Features
+
+TODO: List what your package can do. Maybe include images, gifs, or videos.
+
+## Getting started
+
+TODO: List prerequisites and provide or point to information on how to
+start using the package.
+
+## Usage
+
+TODO: Include short and useful examples for package users. Add longer examples
+to `/example` folder.
+
+```dart
+const like = 'sample';
+```
+
+## Additional information
+
+TODO: Tell users more about the package: where to find more information, how to
+contribute to the package, how to file issues, what response they can expect
+from the package authors, and more.
diff --git a/dev/icon_treeshaker/analysis_options.yaml b/dev/icon_treeshaker/analysis_options.yaml
new file mode 100644
index 0000000000000..dee8927aafeb5
--- /dev/null
+++ b/dev/icon_treeshaker/analysis_options.yaml
@@ -0,0 +1,30 @@
+# This file configures the static analysis results for your project (errors,
+# warnings, and lints).
+#
+# This enables the 'recommended' set of lints from `package:lints`.
+# This set helps identify many issues that may lead to problems when running
+# or consuming Dart code, and enforces writing Dart using a single, idiomatic
+# style and format.
+#
+# If you want a smaller set of lints you can change this to specify
+# 'package:lints/core.yaml'. These are just the most critical lints
+# (the recommended set includes the core lints).
+# The core lints are also what is used by pub.dev for scoring packages.
+
+include: package:lints/recommended.yaml
+
+# Uncomment the following section to specify additional rules.
+
+# linter:
+# rules:
+# - camel_case_types
+
+# analyzer:
+# exclude:
+# - path/to/excluded/files/**
+
+# For more information about the core and recommended set of lints, see
+# https://dart.dev/go/core-lints
+
+# For additional information about configuring this file, see
+# https://dart.dev/guides/language/analysis-options
diff --git a/dev/icon_treeshaker/example/icon_treeshaker_example.dart b/dev/icon_treeshaker/example/icon_treeshaker_example.dart
new file mode 100644
index 0000000000000..3c909885a7914
--- /dev/null
+++ b/dev/icon_treeshaker/example/icon_treeshaker_example.dart
@@ -0,0 +1,6 @@
+import 'package:icon_treeshaker/icon_treeshaker.dart';
+
+void main() {
+ var awesome = Awesome();
+ print('awesome: ${awesome.isAwesome}');
+}
diff --git a/dev/icon_treeshaker/example_flutter/.gitignore b/dev/icon_treeshaker/example_flutter/.gitignore
new file mode 100644
index 0000000000000..79c113f9b5017
--- /dev/null
+++ b/dev/icon_treeshaker/example_flutter/.gitignore
@@ -0,0 +1,45 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.build/
+.buildlog/
+.history
+.svn/
+.swiftpm/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.pub-cache/
+.pub/
+/build/
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release
diff --git a/dev/icon_treeshaker/example_flutter/.metadata b/dev/icon_treeshaker/example_flutter/.metadata
new file mode 100644
index 0000000000000..dac569ea96e9a
--- /dev/null
+++ b/dev/icon_treeshaker/example_flutter/.metadata
@@ -0,0 +1,30 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: "4048d66cc5089164c175f64d45e82a81d8aa43f9"
+ channel: "[user-branch]"
+
+project_type: app
+
+# Tracks metadata for the flutter migrate command
+migration:
+ platforms:
+ - platform: root
+ create_revision: 4048d66cc5089164c175f64d45e82a81d8aa43f9
+ base_revision: 4048d66cc5089164c175f64d45e82a81d8aa43f9
+ - platform: android
+ create_revision: 4048d66cc5089164c175f64d45e82a81d8aa43f9
+ base_revision: 4048d66cc5089164c175f64d45e82a81d8aa43f9
+
+ # User provided section
+
+ # List of Local paths (relative to this file) that should be
+ # ignored by the migrate tool.
+ #
+ # Files that are not part of the templates will be ignored by default.
+ unmanaged_files:
+ - 'lib/main.dart'
+ - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/dev/icon_treeshaker/example_flutter/README.md b/dev/icon_treeshaker/example_flutter/README.md
new file mode 100644
index 0000000000000..9b7df9da3cd6f
--- /dev/null
+++ b/dev/icon_treeshaker/example_flutter/README.md
@@ -0,0 +1,3 @@
+# example_flutter
+
+A new Flutter project.
diff --git a/dev/icon_treeshaker/example_flutter/analysis_options.yaml b/dev/icon_treeshaker/example_flutter/analysis_options.yaml
new file mode 100644
index 0000000000000..f9b303465f19b
--- /dev/null
+++ b/dev/icon_treeshaker/example_flutter/analysis_options.yaml
@@ -0,0 +1 @@
+include: package:flutter_lints/flutter.yaml
diff --git a/dev/icon_treeshaker/example_flutter/android/.gitignore b/dev/icon_treeshaker/example_flutter/android/.gitignore
new file mode 100644
index 0000000000000..be3943c96d8ee
--- /dev/null
+++ b/dev/icon_treeshaker/example_flutter/android/.gitignore
@@ -0,0 +1,14 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+.cxx/
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/to/reference-keystore
+key.properties
+**/*.keystore
+**/*.jks
diff --git a/dev/icon_treeshaker/example_flutter/android/app/build.gradle.kts b/dev/icon_treeshaker/example_flutter/android/app/build.gradle.kts
new file mode 100644
index 0000000000000..e302716d68682
--- /dev/null
+++ b/dev/icon_treeshaker/example_flutter/android/app/build.gradle.kts
@@ -0,0 +1,44 @@
+plugins {
+ id("com.android.application")
+ id("kotlin-android")
+ // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
+ id("dev.flutter.flutter-gradle-plugin")
+}
+
+android {
+ namespace = "com.example.example_flutter"
+ compileSdk = flutter.compileSdkVersion
+ ndkVersion = flutter.ndkVersion
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_11.toString()
+ }
+
+ defaultConfig {
+ // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+ applicationId = "com.example.example_flutter"
+ // You can update the following values to match your application needs.
+ // For more information, see: https://flutter.dev/to/review-gradle-config.
+ minSdk = flutter.minSdkVersion
+ targetSdk = flutter.targetSdkVersion
+ versionCode = flutter.versionCode
+ versionName = flutter.versionName
+ }
+
+ buildTypes {
+ release {
+ // TODO: Add your own signing config for the release build.
+ // Signing with the debug keys for now, so `flutter run --release` works.
+ signingConfig = signingConfigs.getByName("debug")
+ }
+ }
+}
+
+flutter {
+ source = "../.."
+}
diff --git a/dev/icon_treeshaker/example_flutter/android/app/src/debug/AndroidManifest.xml b/dev/icon_treeshaker/example_flutter/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000000000..399f6981d5d35
--- /dev/null
+++ b/dev/icon_treeshaker/example_flutter/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/dev/icon_treeshaker/example_flutter/android/app/src/main/AndroidManifest.xml b/dev/icon_treeshaker/example_flutter/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000..d5d47c0312691
--- /dev/null
+++ b/dev/icon_treeshaker/example_flutter/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dev/icon_treeshaker/example_flutter/android/app/src/main/kotlin/com/example/example_flutter/MainActivity.kt b/dev/icon_treeshaker/example_flutter/android/app/src/main/kotlin/com/example/example_flutter/MainActivity.kt
new file mode 100644
index 0000000000000..c4cea59baea63
--- /dev/null
+++ b/dev/icon_treeshaker/example_flutter/android/app/src/main/kotlin/com/example/example_flutter/MainActivity.kt
@@ -0,0 +1,5 @@
+package com.example.example_flutter
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity : FlutterActivity()
diff --git a/dev/icon_treeshaker/example_flutter/android/app/src/main/res/drawable-v21/launch_background.xml b/dev/icon_treeshaker/example_flutter/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 0000000000000..f74085f3f6a2b
--- /dev/null
+++ b/dev/icon_treeshaker/example_flutter/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/dev/icon_treeshaker/example_flutter/android/app/src/main/res/drawable/launch_background.xml b/dev/icon_treeshaker/example_flutter/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 0000000000000..304732f884201
--- /dev/null
+++ b/dev/icon_treeshaker/example_flutter/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/dev/icon_treeshaker/example_flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/dev/icon_treeshaker/example_flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000000000..db77bb4b7b090
Binary files /dev/null and b/dev/icon_treeshaker/example_flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/dev/icon_treeshaker/example_flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/dev/icon_treeshaker/example_flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000000000..17987b79bb8a3
Binary files /dev/null and b/dev/icon_treeshaker/example_flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/dev/icon_treeshaker/example_flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/dev/icon_treeshaker/example_flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000000000..09d4391482be6
Binary files /dev/null and b/dev/icon_treeshaker/example_flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/dev/icon_treeshaker/example_flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/dev/icon_treeshaker/example_flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000000000..d5f1c8d34e7a8
Binary files /dev/null and b/dev/icon_treeshaker/example_flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/dev/icon_treeshaker/example_flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/dev/icon_treeshaker/example_flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000000000..4d6372eebdb28
Binary files /dev/null and b/dev/icon_treeshaker/example_flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/dev/icon_treeshaker/example_flutter/android/app/src/main/res/values-night/styles.xml b/dev/icon_treeshaker/example_flutter/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000000000..06952be745f9f
--- /dev/null
+++ b/dev/icon_treeshaker/example_flutter/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/dev/icon_treeshaker/example_flutter/android/app/src/main/res/values/styles.xml b/dev/icon_treeshaker/example_flutter/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000000000..cb1ef88056edd
--- /dev/null
+++ b/dev/icon_treeshaker/example_flutter/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/dev/icon_treeshaker/example_flutter/android/app/src/profile/AndroidManifest.xml b/dev/icon_treeshaker/example_flutter/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 0000000000000..399f6981d5d35
--- /dev/null
+++ b/dev/icon_treeshaker/example_flutter/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/dev/icon_treeshaker/example_flutter/android/build.gradle.kts b/dev/icon_treeshaker/example_flutter/android/build.gradle.kts
new file mode 100644
index 0000000000000..89176ef44e8c7
--- /dev/null
+++ b/dev/icon_treeshaker/example_flutter/android/build.gradle.kts
@@ -0,0 +1,21 @@
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
+rootProject.layout.buildDirectory.value(newBuildDir)
+
+subprojects {
+ val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
+ project.layout.buildDirectory.value(newSubprojectBuildDir)
+}
+subprojects {
+ project.evaluationDependsOn(":app")
+}
+
+tasks.register("clean") {
+ delete(rootProject.layout.buildDirectory)
+}
diff --git a/dev/icon_treeshaker/example_flutter/android/gradle.properties b/dev/icon_treeshaker/example_flutter/android/gradle.properties
new file mode 100644
index 0000000000000..f018a61817f55
--- /dev/null
+++ b/dev/icon_treeshaker/example_flutter/android/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/dev/icon_treeshaker/example_flutter/android/gradle/wrapper/gradle-wrapper.properties b/dev/icon_treeshaker/example_flutter/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000000..ac3b47926ee56
--- /dev/null
+++ b/dev/icon_treeshaker/example_flutter/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
diff --git a/dev/icon_treeshaker/example_flutter/android/settings.gradle.kts b/dev/icon_treeshaker/example_flutter/android/settings.gradle.kts
new file mode 100644
index 0000000000000..ab39a10a29baf
--- /dev/null
+++ b/dev/icon_treeshaker/example_flutter/android/settings.gradle.kts
@@ -0,0 +1,25 @@
+pluginManagement {
+ val flutterSdkPath = run {
+ val properties = java.util.Properties()
+ file("local.properties").inputStream().use { properties.load(it) }
+ val flutterSdkPath = properties.getProperty("flutter.sdk")
+ require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
+ flutterSdkPath
+ }
+
+ includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
+
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+plugins {
+ id("dev.flutter.flutter-plugin-loader") version "1.0.0"
+ id("com.android.application") version "8.7.3" apply false
+ id("org.jetbrains.kotlin.android") version "2.1.0" apply false
+}
+
+include(":app")
diff --git a/dev/icon_treeshaker/example_flutter/fonts/TwoIcons.ttf b/dev/icon_treeshaker/example_flutter/fonts/TwoIcons.ttf
new file mode 100644
index 0000000000000..788e667c17220
Binary files /dev/null and b/dev/icon_treeshaker/example_flutter/fonts/TwoIcons.ttf differ
diff --git a/dev/icon_treeshaker/example_flutter/hook/build.dart b/dev/icon_treeshaker/example_flutter/hook/build.dart
new file mode 100644
index 0000000000000..652ebf2d4c171
--- /dev/null
+++ b/dev/icon_treeshaker/example_flutter/hook/build.dart
@@ -0,0 +1,14 @@
+import 'package:icon_treeshaker/hook_helper.dart';
+import 'package:native_assets_cli/native_assets_cli.dart';
+
+void main(List arguments) {
+ build(arguments, (input, output) async {
+ output.assets.font.add(
+ FontAsset(
+ family: 'TwoIcons',
+ file: input.packageRoot.resolve('fonts/TwoIcons.ttf'),
+ package: input.packageName,
+ ),
+ );
+ });
+}
diff --git a/dev/icon_treeshaker/example_flutter/lib/main.dart b/dev/icon_treeshaker/example_flutter/lib/main.dart
new file mode 100644
index 0000000000000..bf5ed1ef5d197
--- /dev/null
+++ b/dev/icon_treeshaker/example_flutter/lib/main.dart
@@ -0,0 +1,26 @@
+import 'package:flutter/material.dart';
+
+void main() {
+ runApp(const MainApp());
+}
+
+class MainApp extends StatelessWidget {
+ const MainApp({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return const MaterialApp(
+ home: Scaffold(
+ body: Center(
+ child: Column(children: [Icon(Icons.abc), Icon(TwoIcons.acUnit)]),
+ ),
+ ),
+ );
+ }
+}
+
+@staticIconProvider
+sealed class TwoIcons {
+ static const IconData acUnit = IconData(0xe800, fontFamily: 'TwoIcons');
+ static const IconData whatshot = IconData(0xe801, fontFamily: 'TwoIcons');
+}
diff --git a/dev/icon_treeshaker/example_flutter/linux/.gitignore b/dev/icon_treeshaker/example_flutter/linux/.gitignore
new file mode 100644
index 0000000000000..d3896c98444fb
--- /dev/null
+++ b/dev/icon_treeshaker/example_flutter/linux/.gitignore
@@ -0,0 +1 @@
+flutter/ephemeral
diff --git a/dev/icon_treeshaker/example_flutter/linux/CMakeLists.txt b/dev/icon_treeshaker/example_flutter/linux/CMakeLists.txt
new file mode 100644
index 0000000000000..9d187535bdb72
--- /dev/null
+++ b/dev/icon_treeshaker/example_flutter/linux/CMakeLists.txt
@@ -0,0 +1,128 @@
+# Project-level configuration.
+cmake_minimum_required(VERSION 3.13)
+project(runner LANGUAGES CXX)
+
+# The name of the executable created for the application. Change this to change
+# the on-disk name of your application.
+set(BINARY_NAME "example_flutter")
+# The unique GTK application identifier for this application. See:
+# https://wiki.gnome.org/HowDoI/ChooseApplicationID
+set(APPLICATION_ID "com.example.example_flutter")
+
+# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
+# versions of CMake.
+cmake_policy(SET CMP0063 NEW)
+
+# Load bundled libraries from the lib/ directory relative to the binary.
+set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
+
+# Root filesystem for cross-building.
+if(FLUTTER_TARGET_PLATFORM_SYSROOT)
+ set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
+ set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
+ set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
+ set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+ set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+endif()
+
+# Define build configuration options.
+if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+ set(CMAKE_BUILD_TYPE "Debug" CACHE
+ STRING "Flutter build mode" FORCE)
+ set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+ "Debug" "Profile" "Release")
+endif()
+
+# Compilation settings that should be applied to most targets.
+#
+# Be cautious about adding new options here, as plugins use this function by
+# default. In most cases, you should add new options to specific targets instead
+# of modifying this function.
+function(APPLY_STANDARD_SETTINGS TARGET)
+ target_compile_features(${TARGET} PUBLIC cxx_std_14)
+ target_compile_options(${TARGET} PRIVATE -Wall -Werror)
+ target_compile_options(${TARGET} PRIVATE "$<$>:-O3>")
+ target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>")
+endfunction()
+
+# Flutter library and tool build rules.
+set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
+add_subdirectory(${FLUTTER_MANAGED_DIR})
+
+# System-level dependencies.
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
+
+# Application build; see runner/CMakeLists.txt.
+add_subdirectory("runner")
+
+# Run the Flutter tool portions of the build. This must not be removed.
+add_dependencies(${BINARY_NAME} flutter_assemble)
+
+# Only the install-generated bundle's copy of the executable will launch
+# correctly, since the resources must in the right relative locations. To avoid
+# people trying to run the unbundled copy, put it in a subdirectory instead of
+# the default top-level location.
+set_target_properties(${BINARY_NAME}
+ PROPERTIES
+ RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
+)
+
+
+# Generated plugin build rules, which manage building the plugins and adding
+# them to the application.
+include(flutter/generated_plugins.cmake)
+
+
+# === Installation ===
+# By default, "installing" just makes a relocatable bundle in the build
+# directory.
+set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+ set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
+endif()
+
+# Start with a clean build bundle directory every time.
+install(CODE "
+ file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
+ " COMPONENT Runtime)
+
+set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
+set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
+
+install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
+ COMPONENT Runtime)
+
+install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+ COMPONENT Runtime)
+
+install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+ COMPONENT Runtime)
+
+foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
+ install(FILES "${bundled_library}"
+ DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+ COMPONENT Runtime)
+endforeach(bundled_library)
+
+# Copy the native assets provided by the build.dart from all packages.
+set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
+install(DIRECTORY "${NATIVE_ASSETS_DIR}"
+ DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+ COMPONENT Runtime)
+
+# Fully re-copy the assets directory on each build to avoid having stale files
+# from a previous install.
+set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
+install(CODE "
+ file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
+ " COMPONENT Runtime)
+install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
+ DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
+
+# Install the AOT library on non-Debug builds only.
+if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
+ install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+ COMPONENT Runtime)
+endif()
diff --git a/dev/icon_treeshaker/example_flutter/linux/flutter/CMakeLists.txt b/dev/icon_treeshaker/example_flutter/linux/flutter/CMakeLists.txt
new file mode 100644
index 0000000000000..d5bd01648a96d
--- /dev/null
+++ b/dev/icon_treeshaker/example_flutter/linux/flutter/CMakeLists.txt
@@ -0,0 +1,88 @@
+# This file controls Flutter-level build steps. It should not be edited.
+cmake_minimum_required(VERSION 3.10)
+
+set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
+
+# Configuration provided via flutter tool.
+include(${EPHEMERAL_DIR}/generated_config.cmake)
+
+# TODO: Move the rest of this into files in ephemeral. See
+# https://github.com/flutter/flutter/issues/57146.
+
+# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
+# which isn't available in 3.10.
+function(list_prepend LIST_NAME PREFIX)
+ set(NEW_LIST "")
+ foreach(element ${${LIST_NAME}})
+ list(APPEND NEW_LIST "${PREFIX}${element}")
+ endforeach(element)
+ set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
+endfunction()
+
+# === Flutter Library ===
+# System-level dependencies.
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
+pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
+pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
+
+set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
+
+# Published to parent scope for install step.
+set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
+set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
+set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
+set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
+
+list(APPEND FLUTTER_LIBRARY_HEADERS
+ "fl_basic_message_channel.h"
+ "fl_binary_codec.h"
+ "fl_binary_messenger.h"
+ "fl_dart_project.h"
+ "fl_engine.h"
+ "fl_json_message_codec.h"
+ "fl_json_method_codec.h"
+ "fl_message_codec.h"
+ "fl_method_call.h"
+ "fl_method_channel.h"
+ "fl_method_codec.h"
+ "fl_method_response.h"
+ "fl_plugin_registrar.h"
+ "fl_plugin_registry.h"
+ "fl_standard_message_codec.h"
+ "fl_standard_method_codec.h"
+ "fl_string_codec.h"
+ "fl_value.h"
+ "fl_view.h"
+ "flutter_linux.h"
+)
+list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
+add_library(flutter INTERFACE)
+target_include_directories(flutter INTERFACE
+ "${EPHEMERAL_DIR}"
+)
+target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
+target_link_libraries(flutter INTERFACE
+ PkgConfig::GTK
+ PkgConfig::GLIB
+ PkgConfig::GIO
+)
+add_dependencies(flutter flutter_assemble)
+
+# === Flutter tool backend ===
+# _phony_ is a non-existent file to force this command to run every time,
+# since currently there's no way to get a full input/output list from the
+# flutter tool.
+add_custom_command(
+ OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
+ ${CMAKE_CURRENT_BINARY_DIR}/_phony_
+ COMMAND ${CMAKE_COMMAND} -E env
+ ${FLUTTER_TOOL_ENVIRONMENT}
+ "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
+ ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
+ VERBATIM
+)
+add_custom_target(flutter_assemble DEPENDS
+ "${FLUTTER_LIBRARY}"
+ ${FLUTTER_LIBRARY_HEADERS}
+)
diff --git a/dev/icon_treeshaker/example_flutter/linux/runner/CMakeLists.txt b/dev/icon_treeshaker/example_flutter/linux/runner/CMakeLists.txt
new file mode 100644
index 0000000000000..e97dabc7028e1
--- /dev/null
+++ b/dev/icon_treeshaker/example_flutter/linux/runner/CMakeLists.txt
@@ -0,0 +1,26 @@
+cmake_minimum_required(VERSION 3.13)
+project(runner LANGUAGES CXX)
+
+# Define the application target. To change its name, change BINARY_NAME in the
+# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
+# work.
+#
+# Any new source files that you add to the application should be added here.
+add_executable(${BINARY_NAME}
+ "main.cc"
+ "my_application.cc"
+ "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
+)
+
+# Apply the standard set of build settings. This can be removed for applications
+# that need different build settings.
+apply_standard_settings(${BINARY_NAME})
+
+# Add preprocessor definitions for the application ID.
+add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
+
+# Add dependency libraries. Add any application-specific dependencies here.
+target_link_libraries(${BINARY_NAME} PRIVATE flutter)
+target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
+
+target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
diff --git a/dev/icon_treeshaker/example_flutter/linux/runner/main.cc b/dev/icon_treeshaker/example_flutter/linux/runner/main.cc
new file mode 100644
index 0000000000000..e7c5c54370372
--- /dev/null
+++ b/dev/icon_treeshaker/example_flutter/linux/runner/main.cc
@@ -0,0 +1,6 @@
+#include "my_application.h"
+
+int main(int argc, char** argv) {
+ g_autoptr(MyApplication) app = my_application_new();
+ return g_application_run(G_APPLICATION(app), argc, argv);
+}
diff --git a/dev/icon_treeshaker/example_flutter/linux/runner/my_application.cc b/dev/icon_treeshaker/example_flutter/linux/runner/my_application.cc
new file mode 100644
index 0000000000000..ada38713ef335
--- /dev/null
+++ b/dev/icon_treeshaker/example_flutter/linux/runner/my_application.cc
@@ -0,0 +1,130 @@
+#include "my_application.h"
+
+#include
+#ifdef GDK_WINDOWING_X11
+#include
+#endif
+
+#include "flutter/generated_plugin_registrant.h"
+
+struct _MyApplication {
+ GtkApplication parent_instance;
+ char** dart_entrypoint_arguments;
+};
+
+G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
+
+// Implements GApplication::activate.
+static void my_application_activate(GApplication* application) {
+ MyApplication* self = MY_APPLICATION(application);
+ GtkWindow* window =
+ GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
+
+ // Use a header bar when running in GNOME as this is the common style used
+ // by applications and is the setup most users will be using (e.g. Ubuntu
+ // desktop).
+ // If running on X and not using GNOME then just use a traditional title bar
+ // in case the window manager does more exotic layout, e.g. tiling.
+ // If running on Wayland assume the header bar will work (may need changing
+ // if future cases occur).
+ gboolean use_header_bar = TRUE;
+#ifdef GDK_WINDOWING_X11
+ GdkScreen* screen = gtk_window_get_screen(window);
+ if (GDK_IS_X11_SCREEN(screen)) {
+ const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
+ if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
+ use_header_bar = FALSE;
+ }
+ }
+#endif
+ if (use_header_bar) {
+ GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
+ gtk_widget_show(GTK_WIDGET(header_bar));
+ gtk_header_bar_set_title(header_bar, "example_flutter");
+ gtk_header_bar_set_show_close_button(header_bar, TRUE);
+ gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
+ } else {
+ gtk_window_set_title(window, "example_flutter");
+ }
+
+ gtk_window_set_default_size(window, 1280, 720);
+ gtk_widget_show(GTK_WIDGET(window));
+
+ g_autoptr(FlDartProject) project = fl_dart_project_new();
+ fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
+
+ FlView* view = fl_view_new(project);
+ gtk_widget_show(GTK_WIDGET(view));
+ gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
+
+ fl_register_plugins(FL_PLUGIN_REGISTRY(view));
+
+ gtk_widget_grab_focus(GTK_WIDGET(view));
+}
+
+// Implements GApplication::local_command_line.
+static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) {
+ MyApplication* self = MY_APPLICATION(application);
+ // Strip out the first argument as it is the binary name.
+ self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
+
+ g_autoptr(GError) error = nullptr;
+ if (!g_application_register(application, nullptr, &error)) {
+ g_warning("Failed to register: %s", error->message);
+ *exit_status = 1;
+ return TRUE;
+ }
+
+ g_application_activate(application);
+ *exit_status = 0;
+
+ return TRUE;
+}
+
+// Implements GApplication::startup.
+static void my_application_startup(GApplication* application) {
+ //MyApplication* self = MY_APPLICATION(object);
+
+ // Perform any actions required at application startup.
+
+ G_APPLICATION_CLASS(my_application_parent_class)->startup(application);
+}
+
+// Implements GApplication::shutdown.
+static void my_application_shutdown(GApplication* application) {
+ //MyApplication* self = MY_APPLICATION(object);
+
+ // Perform any actions required at application shutdown.
+
+ G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application);
+}
+
+// Implements GObject::dispose.
+static void my_application_dispose(GObject* object) {
+ MyApplication* self = MY_APPLICATION(object);
+ g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
+ G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
+}
+
+static void my_application_class_init(MyApplicationClass* klass) {
+ G_APPLICATION_CLASS(klass)->activate = my_application_activate;
+ G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
+ G_APPLICATION_CLASS(klass)->startup = my_application_startup;
+ G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown;
+ G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
+}
+
+static void my_application_init(MyApplication* self) {}
+
+MyApplication* my_application_new() {
+ // Set the program name to the application ID, which helps various systems
+ // like GTK and desktop environments map this running application to its
+ // corresponding .desktop file. This ensures better integration by allowing
+ // the application to be recognized beyond its binary name.
+ g_set_prgname(APPLICATION_ID);
+
+ return MY_APPLICATION(g_object_new(my_application_get_type(),
+ "application-id", APPLICATION_ID,
+ "flags", G_APPLICATION_NON_UNIQUE,
+ nullptr));
+}
diff --git a/dev/icon_treeshaker/example_flutter/linux/runner/my_application.h b/dev/icon_treeshaker/example_flutter/linux/runner/my_application.h
new file mode 100644
index 0000000000000..72271d5e41701
--- /dev/null
+++ b/dev/icon_treeshaker/example_flutter/linux/runner/my_application.h
@@ -0,0 +1,18 @@
+#ifndef FLUTTER_MY_APPLICATION_H_
+#define FLUTTER_MY_APPLICATION_H_
+
+#include
+
+G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
+ GtkApplication)
+
+/**
+ * my_application_new:
+ *
+ * Creates a new Flutter-based application.
+ *
+ * Returns: a new #MyApplication.
+ */
+MyApplication* my_application_new();
+
+#endif // FLUTTER_MY_APPLICATION_H_
diff --git a/dev/icon_treeshaker/example_flutter/pubspec.yaml b/dev/icon_treeshaker/example_flutter/pubspec.yaml
new file mode 100644
index 0000000000000..e81cadd989640
--- /dev/null
+++ b/dev/icon_treeshaker/example_flutter/pubspec.yaml
@@ -0,0 +1,27 @@
+name: example_flutter
+description: "A new Flutter project."
+publish_to: 'none'
+version: 0.1.0
+
+environment:
+ sdk: ^3.8.0-133.0.dev
+
+dependencies:
+ flutter:
+ sdk: flutter
+ native_assets_cli: ^0.11.0
+ icon_treeshaker:
+ path: ../
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+ flutter_lints: ^5.0.0
+
+flutter:
+ uses-material-design: true
+
+ fonts:
+ - family: TwoIcons
+ fonts:
+ - asset: fonts/TwoIcons.ttf
diff --git a/dev/icon_treeshaker/example_flutter/test/widget_test.dart b/dev/icon_treeshaker/example_flutter/test/widget_test.dart
new file mode 100644
index 0000000000000..a95133eed5ffa
--- /dev/null
+++ b/dev/icon_treeshaker/example_flutter/test/widget_test.dart
@@ -0,0 +1,30 @@
+// This is a basic Flutter widget test.
+//
+// To perform an interaction with a widget in your test, use the WidgetTester
+// utility in the flutter_test package. For example, you can send tap and scroll
+// gestures. You can also use WidgetTester to find child widgets in the widget
+// tree, read text, and verify that the values of widget properties are correct.
+
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import 'package:example_flutter/main.dart';
+
+void main() {
+ testWidgets('Counter increments smoke test', (WidgetTester tester) async {
+ // Build our app and trigger a frame.
+ await tester.pumpWidget(const MyApp());
+
+ // Verify that our counter starts at 0.
+ expect(find.text('0'), findsOneWidget);
+ expect(find.text('1'), findsNothing);
+
+ // Tap the '+' icon and trigger a frame.
+ await tester.tap(find.byIcon(Icons.add));
+ await tester.pump();
+
+ // Verify that our counter has incremented.
+ expect(find.text('0'), findsNothing);
+ expect(find.text('1'), findsOneWidget);
+ });
+}
diff --git a/dev/icon_treeshaker/hook/link.dart b/dev/icon_treeshaker/hook/link.dart
new file mode 100644
index 0000000000000..37ff27b3e8fc4
--- /dev/null
+++ b/dev/icon_treeshaker/hook/link.dart
@@ -0,0 +1,136 @@
+import 'dart:convert' show jsonDecode;
+import 'dart:io' as io;
+
+import 'package:collection/collection.dart';
+import 'package:file/file.dart';
+import 'package:file/local.dart';
+import 'package:icon_treeshaker/flutter_config.dart';
+import 'package:icon_treeshaker/hook_helper.dart';
+import 'package:icon_treeshaker/icon_treeshaker.dart';
+import 'package:logging/logging.dart';
+import 'package:native_assets_cli/data_assets.dart';
+import 'package:native_assets_cli/src/config.dart';
+import 'package:path/path.dart' as p;
+import 'package:record_use/record_use.dart' as record_use;
+
+Future main(List arguments) async => await link(arguments, (
+ input,
+ output,
+) async {
+ final logger = Logger('link');
+ final fontAssets = input.assets.fonts.toList();
+ FileSystem fs = LocalFileSystem();
+ var usages = input.usages;
+ //TODO(mosum): Get path to subset from flutter
+ final fontSubset = input.config.flutter.fontSubsetBinary;
+ if (fontSubset == null) {
+ throw ArgumentError(
+ 'The font subset binary was not provided by $input - icon tree shaking is not possible',
+ );
+ }
+ logger.info('Getting tree-shaker data for icons.');
+ final Map pathToTreeshakeData = getIconData(
+ usages,
+ fontAssets,
+ );
+ for (final fontAsset in fontAssets) {
+ logger.info('Checking if $fontAsset should be tree-shaken.');
+ final IconTreeShakerData? iconData = pathToTreeshakeData[fontAsset];
+ if (iconData == null) {
+ logger.info('No IconData usages of $fontAsset found.');
+ continue;
+ }
+ final fontFile = fs.file(fontAsset.file);
+ logger.info('Running $fontSubset on $fontFile.');
+ var pathOfSubsetFont = input.outputDirectoryShared.resolve(
+ //TODO(mosum): Handle duplicates
+ p.basename(fontAsset.file.path),
+ );
+ final success = await subsetFont(
+ fontFile: fontFile,
+ outputPath: pathOfSubsetFont.toFilePath(),
+ iconData: iconData,
+ fs: fs,
+ pathToSubsetBinary: fontSubset,
+ logger: logger,
+ );
+ output.assets.font.add(fontAsset.copyWith(file: pathOfSubsetFont));
+ if (!success) {
+ throw io.ProcessException(
+ fontSubset,
+ [],
+ 'Failure when trying to treeshake $fontAsset. Check the log for more details.',
+ );
+ }
+ logger.info('Success, shrunk $fontFile!.');
+ }
+});
+
+/// The key is the family, or might be prefixed
+/// [result[familyKey] = asset;](https://github.com/flutter/flutter/blob/08b1bdecd6a632e7b30901efaaf19d0f759bd0f5/packages/flutter_tools/lib/src/build_system/targets/icon_tree_shaker.dart#L278)
+///
+/// The [ids] should
+Map getIconData(
+ record_use.RecordedUsages usages,
+ List fonts,
+) {
+ final groupedInstances = (usages.instancesOf(
+ record_use.Identifier(
+ importUri: 'package:flutter/widgets.dart',
+ name: 'IconData',
+ ),
+ ) ??
+ [])
+ .map((e) => e.instanceConstant)
+ .map((e) => e.fields)
+ .map(
+ (e) => (
+ codePoint: (e['codePoint'] as record_use.IntConstant).value,
+ family: (e['fontFamily'] as record_use.StringConstant?)?.value,
+ fontPackage: (e['fontPackage'] as record_use.StringConstant?)?.value,
+ matchTextDirection:
+ (e['matchTextDirection'] as record_use.StringConstant?)?.value,
+ ),
+ )
+ .groupListsBy(
+ (instance) => (family: instance.family, package: instance.fontPackage),
+ );
+ final map = {};
+ for (final MapEntry(key: key, value: instances) in groupedInstances.entries) {
+ var font = fonts.firstWhereOrNull(
+ (font) => font.family == key.family && font.package == key.package,
+ );
+ if (font != null) {
+ // Add space as an optional code point, as web uses it to measure the font height.
+ //TODO(mosum): add web as OS, and pass it to the data config (TBC)
+ bool isWeb = 1 == 0;
+ const int kSpacePoint = 32;
+ final List optionalCodePoints = isWeb ? [kSpacePoint] : [];
+ map[font] = IconTreeShakerData(
+ family: font.family,
+ codePoints: instances.map((instance) => instance.codePoint).toList(),
+ optionalCodePoints: optionalCodePoints,
+ );
+ }
+ }
+
+ // As in https://github.com/flutter/flutter/blob/08b1bdecd6a632e7b30901efaaf19d0f759bd0f5/packages/flutter_tools/lib/src/build_system/targets/icon_tree_shaker.dart#L358
+ // Use 'packages/$package/$family' as keys
+
+ return map;
+}
+
+extension on LinkInput {
+ record_use.RecordedUsages get usages {
+ final usagesFile = recordedUsagesFile;
+ final usagesContent = io.File.fromUri(usagesFile!).readAsStringSync();
+ final usagesJson = jsonDecode(usagesContent) as Map;
+ return record_use.RecordedUsages.fromJson(usagesJson);
+ }
+}
+
+extension on LinkInputAssets {
+ Iterable get fonts => encodedAssets
+ .where((e) => e.type == FontAsset.type)
+ .map(FontAsset.fromEncoded);
+}
diff --git a/dev/icon_treeshaker/lib/flutter_config.dart b/dev/icon_treeshaker/lib/flutter_config.dart
new file mode 100644
index 0000000000000..0f02e2bc19dfd
--- /dev/null
+++ b/dev/icon_treeshaker/lib/flutter_config.dart
@@ -0,0 +1,71 @@
+// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:native_assets_cli/native_assets_cli_builder.dart';
+
+/// Extension to the [HookConfig] providing access to configuration specific
+/// to code assets (only available if code assets are supported).
+extension FlutterHookConfig on HookConfig {
+ /// Code asset specific configuration.
+ FlutterConfig get flutter => FlutterConfig.fromJson(json);
+}
+
+/// Configuration for hook writers if code assets are supported.
+class FlutterConfig {
+ factory FlutterConfig.fromJson(Map json) {
+ return FlutterConfig._(
+ fontSubsetBinary: json.getNested([
+ _configKey,
+ _flutterKey,
+ _fontSubsetBinaryKey,
+ ]),
+ );
+ }
+ // Should not be made public, class will be replaced as a view on `json`.
+ FlutterConfig._({String? fontSubsetBinary})
+ : _fontSubsetBinary = fontSubsetBinary;
+
+ final String? _fontSubsetBinary;
+
+ String? get fontSubsetBinary => _fontSubsetBinary;
+}
+
+/// Extension to initialize code specific configuration on link/build inputs.
+extension CodeAssetBuildInputBuilder on HookConfigBuilder {
+ void setupFlutter({String? fontSubsetBinary}) {
+ json.setNested([
+ _configKey,
+ _flutterKey,
+ _fontSubsetBinaryKey,
+ ], fontSubsetBinary);
+ }
+}
+
+const String _configKey = 'config';
+const String _flutterKey = 'flutter';
+const String _fontSubsetBinaryKey = 'fontSubsetBinary';
+
+extension MapJsonUtils on Map {
+ void setNested(List nestedMapKeys, Object? value) {
+ Map map = this;
+ for (final String key in nestedMapKeys.sublist(
+ 0,
+ nestedMapKeys.length - 1,
+ )) {
+ map = (map[key] ??= {}) as Map;
+ }
+ map[nestedMapKeys.last] = value;
+ }
+
+ T? getNested(List nestedMapKeys) {
+ Map map = this;
+ for (final String key in nestedMapKeys.sublist(
+ 0,
+ nestedMapKeys.length - 1,
+ )) {
+ map = (map[key] ??= {}) as Map;
+ }
+ return map[nestedMapKeys.last] as T?;
+ }
+}
diff --git a/dev/icon_treeshaker/lib/hook_helper.dart b/dev/icon_treeshaker/lib/hook_helper.dart
new file mode 100644
index 0000000000000..607bdb810659e
--- /dev/null
+++ b/dev/icon_treeshaker/lib/hook_helper.dart
@@ -0,0 +1,146 @@
+// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection';
+
+import 'package:native_assets_cli/data_assets.dart';
+import 'package:native_assets_cli/native_assets_cli.dart';
+
+/// Data bundled with a Dart or Flutter application.
+///
+/// A data asset is accessible in a Dart or Flutter application. To retrieve an
+/// asset at runtime, the [id] is used. This enables access to the asset
+/// irrespective of how and where the application is run.
+///
+/// An data asset must provide a [DataAsset.file]. The Dart and Flutter SDK will
+/// bundle this code in the final application.
+final class FontAsset {
+ final String family;
+
+ /// https://api.flutter.dev/flutter/dart-ui/FontStyle.html values - italic or normal
+ final String? style;
+
+ /// integer multiple of 100, between 100 and 900
+ final int? weight;
+
+ /// relative path in the package
+ final Uri file;
+
+ /// The package which contains this asset.
+ final String package;
+
+ /// The identifier for this data asset.
+ /// TODO(mosum): don't use family to allow multiple fonts per family
+ String get id => 'package:$package/$family';
+
+ /// Constructs a [DataAsset] from an [EncodedAsset].
+ factory FontAsset.fromEncoded(EncodedAsset asset) {
+ assert(asset.type == DataAsset.type);
+ final jsonMap = asset.encoding;
+ return FontAsset(
+ family: jsonMap[_familyKey] as String,
+ style: jsonMap[_styleKey] as String?,
+ weight: jsonMap[_weightKey] as int?,
+ package: jsonMap[_packageKey] as String,
+ file: Uri.file(jsonMap[_fileKey] as String),
+ );
+ }
+
+ @override
+ bool operator ==(Object other) {
+ if (other is! FontAsset) {
+ return false;
+ }
+ return other.package == package &&
+ other.file == file &&
+ other.family == family &&
+ other.style == style &&
+ other.weight == weight;
+ }
+
+ @override
+ int get hashCode => Object.hash(package, file, family, style, weight);
+
+ EncodedAsset encode() => EncodedAsset(
+ FontAsset.type,
+ SplayTreeMap.from({
+ _packageKey: package,
+ _fileKey: file.toFilePath(),
+ _familyKey: family,
+ if (style != null) _styleKey: style,
+ if (weight != null) _weightKey: weight,
+ }),
+ );
+
+ @override
+ String toString() => 'FontAsset(${encode().encoding})';
+
+ static const String type = 'font';
+
+ FontAsset({
+ required this.file,
+ required this.package,
+ required this.family,
+ this.style,
+ this.weight,
+ });
+
+ FontAsset copyWith({
+ String? family,
+ String? style,
+ int? weight,
+ Uri? file,
+ String? package,
+ }) => FontAsset(
+ family: family ?? this.family,
+ style: style ?? this.style,
+ weight: weight ?? this.weight,
+ file: file ?? this.file,
+ package: package ?? this.package,
+ );
+}
+
+const _familyKey = 'family';
+const _styleKey = 'style';
+const _weightKey = 'weight';
+const _packageKey = 'package';
+const _fileKey = 'file';
+
+/// Extension to the [LinkOutputBuilder] providing access to emitting data
+/// assets (only available if data assets are supported).
+extension FontAssetLinkOutputBuilder on EncodedAssetLinkOutputBuilder {
+ /// Provides access to emitting data assets.
+ FontAssetLinkOutputBuilderAdd get font => FontAssetLinkOutputBuilderAdd(this);
+}
+
+/// Extension on [LinkOutputBuilder] to emit data assets.
+extension type FontAssetLinkOutputBuilderAdd(
+ EncodedAssetLinkOutputBuilder _output
+) {
+ /// Adds the given [asset] to the link hook output.
+ void add(FontAsset asset) => _output.addEncodedAsset(asset.encode());
+
+ /// Adds the given [assets] to the link hook output.
+ void addAll(Iterable assets) => assets.forEach(add);
+}
+
+/// Extension to the [LinkOutputBuilder] providing access to emitting data
+/// assets (only available if data assets are supported).
+extension FontAssetBuildOutputBuilder on EncodedAssetBuildOutputBuilder {
+ /// Provides access to emitting data assets.
+ FontAssetBuildOutputBuilderAdd get font =>
+ FontAssetBuildOutputBuilderAdd(this);
+}
+
+/// Extension on [LinkOutputBuilder] to emit data assets.
+extension type FontAssetBuildOutputBuilderAdd(
+ EncodedAssetBuildOutputBuilder _output
+) {
+ /// Adds the given [asset] to the link hook output.
+ void add(FontAsset asset) =>
+ _output.addEncodedAsset(asset.encode(), linkInPackage: 'icon_treeshaker');
+
+ /// Adds the given [assets] to the link hook output.
+ void addAll(Iterable assets) => assets.forEach(add);
+}
diff --git a/dev/icon_treeshaker/lib/icon_treeshaker.dart b/dev/icon_treeshaker/lib/icon_treeshaker.dart
new file mode 100644
index 0000000000000..6aac066d47d7b
--- /dev/null
+++ b/dev/icon_treeshaker/lib/icon_treeshaker.dart
@@ -0,0 +1,8 @@
+/// Support for doing something awesome.
+///
+/// More dartdocs go here.
+library;
+
+export 'src/icon_treeshaker_base.dart';
+
+// TODO: Export any libraries intended for clients of this package.
diff --git a/dev/icon_treeshaker/lib/src/icon_treeshaker_base.dart b/dev/icon_treeshaker/lib/src/icon_treeshaker_base.dart
new file mode 100644
index 0000000000000..12091caf9a5b8
--- /dev/null
+++ b/dev/icon_treeshaker/lib/src/icon_treeshaker_base.dart
@@ -0,0 +1,140 @@
+// TODO: Put public facing types in this file.
+
+import 'dart:convert' show utf8;
+import 'dart:io' show Process;
+
+import 'package:file/file.dart' show File, FileSystem;
+import 'package:logging/logging.dart' show Logger;
+import 'package:mime/mime.dart' as mime;
+
+/// Checks if you are awesome. Spoiler: you are.
+class Awesome {
+ bool get isAwesome => true;
+}
+
+/// The MIME types for supported font sets.
+const Set kTtfMimeTypes = {
+ 'font/ttf', // based on internet search
+ 'font/opentype',
+ 'font/otf',
+ 'application/x-font-opentype',
+ 'application/x-font-otf',
+ 'application/x-font-ttf', // based on running locally.
+};
+
+/// Calls font-subset, which transforms the [fontFile] font file to a
+/// subsetted version at [outputPath].
+///
+/// If [enabled] is false, or the relative path is not recognized as an icon
+/// font used in the Flutter application, this returns false.
+/// If the font-subset subprocess fails, it will [throwToolExit].
+/// Otherwise, it will return true.
+Future subsetFont({
+ required File fontFile,
+ required String outputPath,
+ required IconTreeShakerData iconData,
+ required FileSystem fs,
+ required String pathToSubsetBinary,
+ required Logger logger,
+}) async {
+ if (fontFile.lengthSync() < 12) {
+ return false;
+ }
+ final String? mimeType = mime.lookupMimeType(
+ fontFile.path,
+ headerBytes: await fontFile.openRead(0, 12).first,
+ );
+ if (!kTtfMimeTypes.contains(mimeType)) {
+ return false;
+ }
+
+ final File fontSubset = fs.file(pathToSubsetBinary);
+
+ if (!fontSubset.existsSync()) {
+ throw IconTreeShakerException._(
+ 'The font-subset utility is missing. Run "flutter doctor".',
+ );
+ }
+
+ final List args = [outputPath, fontFile.path];
+ final Iterable requiredCodePointStrings = iconData.codePoints.map(
+ (int codePoint) => codePoint.toString(),
+ );
+ final Iterable optionalCodePointStrings = iconData.optionalCodePoints
+ .map((int codePoint) => 'optional:$codePoint');
+ final String codePointsString = requiredCodePointStrings
+ .followedBy(optionalCodePointStrings)
+ .join(' ');
+ logger.info(
+ 'Running font-subset: ${args.join(' ')}, '
+ 'using codepoints $codePointsString',
+ );
+ final Process fontSubsetProcess = await Process.start(fontSubset.path, args);
+ try {
+ fontSubsetProcess.stdin.write(codePointsString);
+ await fontSubsetProcess.stdin.flush();
+ await fontSubsetProcess.stdin.close();
+ } on Exception {
+ // handled by checking the exit code.
+ }
+
+ final int code = await fontSubsetProcess.exitCode;
+ if (code != 0) {
+ logger.severe(await utf8.decodeStream(fontSubsetProcess.stdout));
+ logger.severe(await utf8.decodeStream(fontSubsetProcess.stderr));
+ throw IconTreeShakerException._(
+ 'Font subsetting failed with exit code $code.',
+ );
+ }
+ logger.info(getSubsetSummaryMessage(fontFile, fs.file(outputPath)));
+ return true;
+}
+
+/// The font family name, relative path to font file, and list of code points
+/// the application is using.
+class IconTreeShakerData {
+ /// All parameters are required.
+ const IconTreeShakerData({
+ required this.family,
+ required this.codePoints,
+ required this.optionalCodePoints,
+ });
+
+ /// The font family name, e.g. "MaterialIcons".
+ final String family;
+
+ /// The list of code points for the font.
+ final List codePoints;
+
+ /// The list of code points to be optionally added, if they exist in the
+ /// input font. Otherwise, the tool will silently omit them.
+ final List optionalCodePoints;
+
+ @override
+ String toString() => 'FontSubsetData($family, $codePoints)';
+}
+
+class IconTreeShakerException implements Exception {
+ IconTreeShakerException._(this.message);
+
+ final String message;
+
+ @override
+ String toString() =>
+ 'IconTreeShakerException: $message\n\n'
+ 'To disable icon tree shaking, pass --no-tree-shake-icons to the requested '
+ 'flutter build command';
+}
+
+String getSubsetSummaryMessage(File inputFont, File outputFont) {
+ final String fontName = inputFont.basename;
+ final double inputSize = inputFont.lengthSync().toDouble();
+ final double outputSize = outputFont.lengthSync().toDouble();
+ final double reductionBytes = inputSize - outputSize;
+ final String reductionPercentage = (reductionBytes / inputSize * 100)
+ .toStringAsFixed(1);
+ return 'Font asset "$fontName" was tree-shaken, reducing it from '
+ '${inputSize.ceil()} to ${outputSize.ceil()} bytes '
+ '($reductionPercentage% reduction). Tree-shaking can be disabled '
+ 'by providing the --no-tree-shake-icons flag when building your app.';
+}
diff --git a/dev/icon_treeshaker/pubspec.yaml b/dev/icon_treeshaker/pubspec.yaml
new file mode 100644
index 0000000000000..926606eb07598
--- /dev/null
+++ b/dev/icon_treeshaker/pubspec.yaml
@@ -0,0 +1,22 @@
+name: icon_treeshaker
+description: A starting point for Dart libraries or applications.
+version: 1.0.0
+# repository: https://github.com/my_org/my_repo
+
+environment:
+ sdk: ^3.8.0-133.0.dev
+
+# Add regular dependencies here.
+dependencies:
+ collection: ^1.19.1
+ file: ^7.0.1
+ logging: ^1.3.0
+ mime: ^2.0.0
+ native_assets_cli: ^0.11.0
+ path: ^1.9.1
+ record_use: ^0.3.0
+ # path: ^1.8.0
+
+dev_dependencies:
+ lints: ^5.0.0
+ test: ^1.24.0
diff --git a/dev/icon_treeshaker/test/icon_treeshaker_test.dart b/dev/icon_treeshaker/test/icon_treeshaker_test.dart
new file mode 100644
index 0000000000000..f703c426ce4d7
--- /dev/null
+++ b/dev/icon_treeshaker/test/icon_treeshaker_test.dart
@@ -0,0 +1,16 @@
+import 'package:icon_treeshaker/icon_treeshaker.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('A group of tests', () {
+ final awesome = Awesome();
+
+ setUp(() {
+ // Additional setup goes here.
+ });
+
+ test('First Test', () {
+ expect(awesome.isAwesome, isTrue);
+ });
+ });
+}
diff --git a/packages/flutter_tools/lib/src/asset.dart b/packages/flutter_tools/lib/src/asset.dart
index 138eb2725a20e..47a5ac7c95803 100644
--- a/packages/flutter_tools/lib/src/asset.dart
+++ b/packages/flutter_tools/lib/src/asset.dart
@@ -16,6 +16,7 @@ import 'base/logger.dart';
import 'base/platform.dart';
import 'base/utils.dart';
import 'build_info.dart';
+import 'build_system/targets/native_assets.dart';
import 'cache.dart';
import 'convert.dart';
import 'dart/package_map.dart';
@@ -112,6 +113,7 @@ abstract class AssetBundle {
/// Returns 0 for success; non-zero for failure.
Future build({
+ DartHookResult? dartHookResult,
String manifestPath = defaultManifestPath,
required String packageConfigPath,
bool deferredComponentsEnabled = false,
@@ -187,6 +189,8 @@ class ManifestAssetBundle implements AssetBundle {
DateTime? _lastBuildTimestamp;
+ DartHookResult? _lastHookResult;
+
// We assume the main asset is designed for a device pixel ratio of 1.0.
static const String _kAssetManifestJsonFilename = 'AssetManifest.json';
static const String _kAssetManifestBinFilename = 'AssetManifest.bin';
@@ -206,13 +210,21 @@ class ManifestAssetBundle implements AssetBundle {
@override
bool needsBuild({String manifestPath = defaultManifestPath}) {
- final DateTime? lastBuildTimestamp = _lastBuildTimestamp;
- if (lastBuildTimestamp == null) {
+ if (!wasBuiltOnce() ||
+ _lastHookResult == null ||
+ // We need to re-run the dart build.
+ !_lastHookResult!.isUpToDate(_fileSystem) ||
+ // We don't have to re-run the dart build, but some files the dart build
+ // wants us to bundle have changed contents.
+ _lastHookResult!.isOutputDirty(_fileSystem)) {
return true;
}
+ final DateTime lastBuildTimestamp = _lastBuildTimestamp!;
+
final FileStat manifestStat = _fileSystem.file(manifestPath).statSync();
- if (manifestStat.type == FileSystemEntityType.notFound) {
+ if (manifestStat.type == FileSystemEntityType.notFound ||
+ manifestStat.modified.isAfter(lastBuildTimestamp)) {
return true;
}
@@ -221,18 +233,19 @@ class ManifestAssetBundle implements AssetBundle {
return true; // directory was deleted.
}
for (final File file in directory.listSync().whereType()) {
- final DateTime dateTime = file.statSync().modified;
- if (dateTime.isAfter(lastBuildTimestamp)) {
+ final DateTime lastModified = file.statSync().modified;
+ if (lastModified.isAfter(lastBuildTimestamp)) {
return true;
}
}
}
- return manifestStat.modified.isAfter(lastBuildTimestamp);
+ return false;
}
@override
Future build({
+ DartHookResult? dartHookResult,
String manifestPath = defaultManifestPath,
FlutterProject? flutterProject,
required String packageConfigPath,
@@ -255,6 +268,7 @@ class ManifestAssetBundle implements AssetBundle {
// hang on hot reload, as the incremental dill files will never be copied to the
// device.
_lastBuildTimestamp = DateTime.now();
+ _lastHookResult = dartHookResult ?? DartHookResult.empty();
if (flutterManifest.isEmpty) {
entries[_kAssetManifestJsonFilename] = AssetBundleEntry(
DevFSStringContent('{}'),
@@ -400,8 +414,39 @@ class ManifestAssetBundle implements AssetBundle {
}
}
+ for (final DataAsset dataAsset in dartHookResult?.dataAssets ?? []) {
+ final Package package = packageConfig[dataAsset.package]!;
+ final Uri fileUri = dataAsset.file;
+
+ final String baseDir;
+ final Uri relativeUri;
+ if (fileUri.isAbsolute) {
+ final List pathSegments = fileUri.pathSegments;
+ baseDir =
+ fileUri.replace(pathSegments: pathSegments.take(pathSegments.length - 1)).toFilePath();
+ relativeUri = Uri(pathSegments: [pathSegments.last]);
+ } else {
+ baseDir = package.root.toFilePath();
+ relativeUri = fileUri;
+ }
+
+ final _Asset asset = _Asset(
+ baseDir: baseDir,
+ relativeUri: relativeUri,
+ entryUri: Uri.parse('packages/${dataAsset.package}/${dataAsset.name}'),
+ package: package,
+ );
+ if (assetVariants.containsKey(asset)) {
+ _logger.printError(
+ 'Conflicting assets: The asset "$asset" was declared in the pubspec and the hook.',
+ );
+ return 1;
+ }
+ assetVariants[asset] = <_Asset>[asset];
+ }
+
// Save the contents of each image, image variant, and font
- // asset in entries.
+ // asset in [entries].
for (final _Asset asset in assetVariants.keys) {
final File assetFile = asset.lookupAssetFile(_fileSystem);
final List<_Asset> variants = assetVariants[asset]!;
diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart
index ac6ce0e8ff149..dde3d231cfcd0 100644
--- a/packages/flutter_tools/lib/src/build_info.dart
+++ b/packages/flutter_tools/lib/src/build_info.dart
@@ -930,20 +930,6 @@ const String kSdkRoot = 'SdkRoot';
/// Whether to enable Dart obfuscation and where to save the symbol map.
const String kDartObfuscation = 'DartObfuscation';
-/// Whether to enable Native Assets.
-///
-/// If true, native assets are built and the mapping for native assets lookup
-/// at runtime is embedded in the kernel file.
-///
-/// If false, native assets are not built, and an empty mapping is embedded in
-/// the kernel file. Used for targets that trigger kernel builds but
-/// are not OS/architecture specific.
-///
-/// Supported values are 'true' and 'false'.
-///
-/// Defaults to 'true'.
-const String kNativeAssets = 'NativeAssets';
-
/// An output directory where one or more code-size measurements may be written.
const String kCodeSizeDirectory = 'CodeSizeDirectory';
diff --git a/packages/flutter_tools/lib/src/build_system/targets/android.dart b/packages/flutter_tools/lib/src/build_system/targets/android.dart
index da89922d3e86e..9fa7039d7303c 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/android.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/android.dart
@@ -70,9 +70,11 @@ abstract class AndroidAssetBundle extends Target {
.file(isolateSnapshotData)
.copySync(outputDirectory.childFile('isolate_snapshot_data').path);
}
+ final DartHookResult dartHookResult = await DartBuild.loadHookResult(environment);
final Depfile assetDepfile = await copyAssets(
environment,
outputDirectory,
+ dartHookResult: dartHookResult,
targetPlatform: TargetPlatform.android,
buildMode: buildMode,
flavor: environment.defines[kFlavor],
@@ -89,7 +91,11 @@ abstract class AndroidAssetBundle extends Target {
}
@override
- List get dependencies => const [KernelSnapshot(), InstallCodeAssets()];
+ List get dependencies => const [
+ DartBuildForNative(),
+ KernelSnapshot(),
+ InstallCodeAssets(),
+ ];
}
/// An implementation of [AndroidAssetBundle] that includes dependencies on vm
diff --git a/packages/flutter_tools/lib/src/build_system/targets/assets.dart b/packages/flutter_tools/lib/src/build_system/targets/assets.dart
index 5072f10ef51fa..9f6eacff9515c 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/assets.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/assets.dart
@@ -33,6 +33,7 @@ import 'native_assets.dart';
Future copyAssets(
Environment environment,
Directory outputDirectory, {
+ required DartHookResult dartHookResult,
Map additionalContent = const {},
required TargetPlatform targetPlatform,
required BuildMode buildMode,
@@ -49,6 +50,7 @@ Future copyAssets(
splitDeferredAssets: buildMode != BuildMode.debug && buildMode != BuildMode.jitRelease,
).createBundle();
final int resultCode = await assetBundle.build(
+ dartHookResult: dartHookResult,
manifestPath: pubspecFile.path,
packageConfigPath: findPackageConfigFileOrDefault(environment.projectDir).path,
deferredComponentsEnabled: environment.defines[kDeferredComponents] == 'true',
@@ -241,7 +243,11 @@ class CopyAssets extends Target {
String get name => 'copy_assets';
@override
- List get dependencies => const [KernelSnapshot(), InstallCodeAssets()];
+ List get dependencies => const [
+ DartBuildForNative(),
+ KernelSnapshot(),
+ InstallCodeAssets(),
+ ];
@override
List get inputs => const [
@@ -267,9 +273,11 @@ class CopyAssets extends Target {
final BuildMode buildMode = BuildMode.fromCliName(buildModeEnvironment);
final Directory output = environment.buildDir.childDirectory('flutter_assets');
output.createSync(recursive: true);
+ final DartHookResult dartHookResult = await DartBuild.loadHookResult(environment);
final Depfile depfile = await copyAssets(
environment,
output,
+ dartHookResult: dartHookResult,
targetPlatform: TargetPlatform.android,
buildMode: buildMode,
flavor: environment.defines[kFlavor],
diff --git a/packages/flutter_tools/lib/src/build_system/targets/common.dart b/packages/flutter_tools/lib/src/build_system/targets/common.dart
index b1f60007c23d4..77f0cdd4c38e1 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/common.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/common.dart
@@ -84,9 +84,11 @@ class CopyFlutterBundle extends Target {
.file(isolateSnapshotData)
.copySync(environment.outputDir.childFile('isolate_snapshot_data').path);
}
+ final DartHookResult dartHookResult = await DartBuild.loadHookResult(environment);
final Depfile assetDepfile = await copyAssets(
environment,
environment.outputDir,
+ dartHookResult: dartHookResult,
targetPlatform: TargetPlatform.android,
buildMode: buildMode,
flavor: flavor,
@@ -103,7 +105,11 @@ class CopyFlutterBundle extends Target {
}
@override
- List get dependencies => const [KernelSnapshot(), InstallCodeAssets()];
+ List get dependencies => const [
+ DartBuildForNative(),
+ KernelSnapshot(),
+ InstallCodeAssets(),
+ ];
}
/// Copies the pre-built flutter bundle for release mode.
@@ -153,7 +159,7 @@ class KernelSnapshot extends Target {
@override
List get outputs => const [
Source.pattern('{BUILD_DIR}/${KernelSnapshot.dillName}'),
- // TODO(mosuem): Should output resources.json. https://github.com/flutter/flutter/issues/146263
+ Source.pattern('{BUILD_DIR}/${KernelSnapshot.recordedUsagesName}'),
];
static const String depfile = 'kernel_snapshot_program.d';
@@ -168,6 +174,7 @@ class KernelSnapshot extends Target {
];
static const String dillName = 'app.dill';
+ static const String recordedUsagesName = 'recorded_usages.json';
@override
Future build(Environment environment) async {
@@ -282,6 +289,7 @@ class KernelSnapshot extends Target {
buildDir: environment.buildDir,
targetOS: targetOS,
checkDartPluginRegistry: environment.generateDartPluginRegistry,
+ recordedUsages: environment.buildDir.childFile(KernelSnapshot.recordedUsagesName).path,
);
if (output == null || output.errorCount != 0) {
throw Exception();
diff --git a/packages/flutter_tools/lib/src/build_system/targets/ios.dart b/packages/flutter_tools/lib/src/build_system/targets/ios.dart
index 37331f279a8d8..fc983e0a94931 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/ios.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/ios.dart
@@ -443,7 +443,11 @@ abstract class IosAssetBundle extends Target {
const IosAssetBundle();
@override
- List get dependencies => const [KernelSnapshot(), InstallCodeAssets()];
+ List get dependencies => const [
+ DartBuildForNative(),
+ KernelSnapshot(),
+ InstallCodeAssets(),
+ ];
@override
List get inputs => const [
@@ -529,9 +533,11 @@ abstract class IosAssetBundle extends Target {
final String? flavor = await flutterProject.ios.parseFlavorFromConfiguration(environment);
// Copy the assets.
+ final DartHookResult dartHookResult = await DartBuild.loadHookResult(environment);
final Depfile assetDepfile = await copyAssets(
environment,
assetDirectory,
+ dartHookResult: dartHookResult,
targetPlatform: TargetPlatform.ios,
buildMode: buildMode,
additionalInputs: [
diff --git a/packages/flutter_tools/lib/src/build_system/targets/linux.dart b/packages/flutter_tools/lib/src/build_system/targets/linux.dart
index 8be8524307d09..0d03a40cb7e98 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/linux.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/linux.dart
@@ -93,6 +93,7 @@ abstract class BundleLinuxAssets extends Target {
@override
List get dependencies => [
+ const DartBuildForNative(),
const KernelSnapshot(),
const InstallCodeAssets(),
UnpackLinux(targetPlatform),
@@ -127,9 +128,11 @@ abstract class BundleLinuxAssets extends Target {
.copySync(outputDirectory.childFile('kernel_blob.bin').path);
}
final String versionInfo = getVersionInfo(environment.defines);
+ final DartHookResult dartHookResult = await DartBuild.loadHookResult(environment);
final Depfile depfile = await copyAssets(
environment,
outputDirectory,
+ dartHookResult: dartHookResult,
targetPlatform: targetPlatform,
buildMode: buildMode,
additionalContent: {
diff --git a/packages/flutter_tools/lib/src/build_system/targets/macos.dart b/packages/flutter_tools/lib/src/build_system/targets/macos.dart
index 8ba45dd5786fe..74cbc24701576 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/macos.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/macos.dart
@@ -430,6 +430,9 @@ class CompileMacOSFramework extends Target {
abstract class MacOSBundleFlutterAssets extends Target {
const MacOSBundleFlutterAssets();
+ @override
+ List get dependencies => const [DartBuildForNative()];
+
@override
List get inputs => const [
Source.pattern('{BUILD_DIR}/App.framework/App'),
@@ -491,9 +494,11 @@ abstract class MacOSBundleFlutterAssets extends Target {
final FlutterProject flutterProject = FlutterProject.fromDirectory(environment.projectDir);
final String? flavor = await flutterProject.macos.parseFlavorFromConfiguration(environment);
+ final DartHookResult dartHookResult = await DartBuild.loadHookResult(environment);
final Depfile assetDepfile = await copyAssets(
environment,
assetDirectory,
+ dartHookResult: dartHookResult,
targetPlatform: TargetPlatform.darwin,
buildMode: buildMode,
flavor: flavor,
@@ -613,11 +618,12 @@ class DebugMacOSBundleFlutterAssets extends MacOSBundleFlutterAssets {
String get name => 'debug_macos_bundle_flutter_assets';
@override
- List get dependencies => const [
- KernelSnapshot(),
- DebugMacOSFramework(),
- DebugUnpackMacOS(),
- InstallCodeAssets(),
+ List get dependencies => [
+ ...super.dependencies,
+ const KernelSnapshot(),
+ const DebugMacOSFramework(),
+ const DebugUnpackMacOS(),
+ const InstallCodeAssets(),
];
@override
@@ -659,10 +665,11 @@ class ProfileMacOSBundleFlutterAssets extends MacOSBundleFlutterAssets {
String get name => 'profile_macos_bundle_flutter_assets';
@override
- List get dependencies => const [
- CompileMacOSFramework(),
- InstallCodeAssets(),
- ProfileUnpackMacOS(),
+ List get dependencies => [
+ ...super.dependencies,
+ const CompileMacOSFramework(),
+ const ProfileUnpackMacOS(),
+ const InstallCodeAssets(),
];
@override
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 c98e33d372a5e..206240aaaaf44 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
@@ -5,6 +5,7 @@
import 'package:meta/meta.dart';
import 'package:package_config/package_config_types.dart';
+import '../../artifacts.dart';
import '../../base/common.dart';
import '../../base/file_system.dart';
import '../../build_info.dart';
@@ -13,61 +14,69 @@ import '../../dart/package_map.dart';
import '../../isolated/native_assets/native_assets.dart';
import '../build_system.dart';
import '../depfile.dart';
-import '../exceptions.dart';
+import '../exceptions.dart' show MissingDefineException;
import 'common.dart';
+export '../../isolated/native_assets/native_assets.dart'
+ show CodeAsset, DartHookResult, DataAsset, DynamicLoadingBundled;
+
/// Runs the dart build of the app.
-abstract class DartBuild extends Target {
- const DartBuild({@visibleForTesting FlutterNativeAssetsBuildRunner? buildRunner})
- : _buildRunner = buildRunner;
+class DartBuild extends Target {
+ const DartBuild({
+ @visibleForTesting FlutterNativeAssetsBuildRunner? buildRunner,
+ this.specifiedTargetPlatform,
+ }) : _buildRunner = buildRunner;
final FlutterNativeAssetsBuildRunner? _buildRunner;
+ final TargetPlatform? specifiedTargetPlatform;
+
@override
Future build(Environment environment) async {
final FileSystem fileSystem = environment.fileSystem;
- final String? nativeAssetsEnvironment = environment.defines[kNativeAssets];
-
- final DartBuildResult result;
- if (nativeAssetsEnvironment == 'false') {
- result = const DartBuildResult.empty();
- } else {
- final TargetPlatform targetPlatform = _getTargetPlatformFromEnvironment(environment, name);
-
- final PackageConfig packageConfig = await loadPackageConfigWithLogging(
- fileSystem.file(environment.packageConfigPath),
- logger: environment.logger,
- );
- final Uri projectUri = environment.projectDir.uri;
- final String? runPackageName =
- packageConfig.packages.where((Package p) => p.root == projectUri).firstOrNull?.name;
- final FlutterNativeAssetsBuildRunner buildRunner =
- _buildRunner ??
- FlutterNativeAssetsBuildRunnerImpl(
- environment.packageConfigPath,
- packageConfig,
- fileSystem,
- environment.logger,
- runPackageName!,
- );
- result = await runFlutterSpecificDartBuild(
- environmentDefines: environment.defines,
- buildRunner: buildRunner,
- targetPlatform: targetPlatform,
- projectUri: projectUri,
- fileSystem: fileSystem,
- );
- }
+ final DartHookResult result;
+
+ final TargetPlatform targetPlatform =
+ specifiedTargetPlatform ?? _getTargetPlatformFromEnvironment(environment, name);
- final File dartBuildResultJsonFile = environment.buildDir.childFile(dartBuildResultFilename);
- if (!dartBuildResultJsonFile.parent.existsSync()) {
- dartBuildResultJsonFile.parent.createSync(recursive: true);
+ final PackageConfig packageConfig = await loadPackageConfigWithLogging(
+ fileSystem.file(environment.packageConfigPath),
+ logger: environment.logger,
+ );
+ final Uri projectUri = environment.projectDir.uri;
+ final String? runPackageName =
+ packageConfig.packages.where((Package p) => p.root == projectUri).firstOrNull?.name;
+ final FlutterNativeAssetsBuildRunner buildRunner =
+ _buildRunner ??
+ FlutterNativeAssetsBuildRunnerImpl(
+ environment.packageConfigPath,
+ packageConfig,
+ fileSystem,
+ environment.logger,
+ runPackageName!,
+ );
+ result = await runFlutterSpecificHooks(
+ environmentDefines: {
+ ...environment.defines,
+ Artifact.fontSubset.name: environment.artifacts.getArtifactPath(Artifact.fontSubset),
+ KernelSnapshot.recordedUsagesName:
+ environment.buildDir.childFile(KernelSnapshot.recordedUsagesName).path,
+ },
+ buildRunner: buildRunner,
+ targetPlatform: targetPlatform,
+ projectUri: projectUri,
+ fileSystem: fileSystem,
+ );
+
+ final File dartHookResultJsonFile = environment.buildDir.childFile(dartHookResultFilename);
+ if (!dartHookResultJsonFile.parent.existsSync()) {
+ dartHookResultJsonFile.parent.createSync(recursive: true);
}
- dartBuildResultJsonFile.writeAsStringSync(json.encode(result.toJson()));
+ dartHookResultJsonFile.writeAsStringSync(json.encode(result.toJson()));
final Depfile depfile = Depfile(
[for (final Uri dependency in result.dependencies) fileSystem.file(dependency)],
- [fileSystem.file(dartBuildResultJsonFile)],
+ [fileSystem.file(dartHookResultJsonFile)],
);
final File outputDepfile = environment.buildDir.childFile(depFilename);
if (!outputDepfile.parent.existsSync()) {
@@ -89,29 +98,33 @@ abstract class DartBuild extends Target {
),
// If different packages are resolved, different native assets might need to be built.
Source.pattern('{WORKSPACE_DIR}/.dart_tool/package_config_subset'),
- // TODO(mosuem): Should consume resources.json. https://github.com/flutter/flutter/issues/146263
+ Source.pattern('{BUILD_DIR}/${KernelSnapshot.recordedUsagesName}'),
];
@override
String get name => 'dart_build';
@override
- List get outputs => const [
- Source.pattern('{BUILD_DIR}/$dartBuildResultFilename'),
- ];
+ List get outputs => const [Source.pattern('{BUILD_DIR}/$dartHookResultFilename')];
/// Dependent build [Target]s can use this to consume the result of the
/// [DartBuild] target.
- static Future loadBuildResult(Environment environment) async {
- final File dartBuildResultJsonFile = environment.buildDir.childFile(
- DartBuild.dartBuildResultFilename,
+ static Future loadHookResult(Environment environment) async {
+ final File dartHookResultJsonFile = environment.buildDir.childFile(
+ DartBuild.dartHookResultFilename,
);
- return DartBuildResult.fromJson(
- json.decode(dartBuildResultJsonFile.readAsStringSync()) as Map,
+ if (!dartHookResultJsonFile.existsSync()) {
+ return DartHookResult.empty();
+ }
+ return DartHookResult.fromJson(
+ json.decode(dartHookResultJsonFile.readAsStringSync()) as Map,
);
}
- static const String dartBuildResultFilename = 'dart_build_result.json';
+ @override
+ List get dependencies => [];
+
+ static const String dartHookResultFilename = 'dart_build_result.json';
static const String depFilename = 'dart_build.d';
}
@@ -137,13 +150,13 @@ class InstallCodeAssets extends Target {
final TargetPlatform targetPlatform = _getTargetPlatformFromEnvironment(environment, name);
// We fetch the result from the [DartBuild].
- final DartBuildResult dartBuildResult = await DartBuild.loadBuildResult(environment);
+ final DartHookResult dartHookResult = await DartBuild.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.
final Uri nativeAssetsFileUri = environment.buildDir.childFile(nativeAssetsFilename).uri;
await installCodeAssets(
- dartBuildResult: dartBuildResult,
+ dartHookResult: dartHookResult,
environmentDefines: environment.defines,
targetPlatform: targetPlatform,
projectUri: projectUri,
@@ -153,7 +166,7 @@ class InstallCodeAssets extends Target {
assert(await fileSystem.file(nativeAssetsFileUri).exists());
final Depfile depfile = Depfile(
- [for (final Uri file in dartBuildResult.filesToBeBundled) fileSystem.file(file)],
+ [for (final Uri file in dartHookResult.filesToBeBundled) fileSystem.file(file)],
[fileSystem.file(nativeAssetsFileUri)],
);
final File outputDepfile = environment.buildDir.childFile(depFilename);
@@ -176,6 +189,7 @@ class InstallCodeAssets extends Target {
),
// If different packages are resolved, different native assets might need to be built.
Source.pattern('{WORKSPACE_DIR}/.dart_tool/package_config_subset'),
+ Source.artifact(Artifact.fontSubset),
];
@override
diff --git a/packages/flutter_tools/lib/src/build_system/targets/web.dart b/packages/flutter_tools/lib/src/build_system/targets/web.dart
index 130caa7d4bfe8..67bafd481cb28 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/web.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/web.dart
@@ -28,6 +28,7 @@ import '../depfile.dart';
import '../exceptions.dart';
import 'assets.dart';
import 'localizations.dart';
+import 'native_assets.dart';
/// Generates an entry point for a web target.
// Keep this in sync with build_runner/resident_web_runner.dart
@@ -408,7 +409,11 @@ class WebReleaseBundle extends Target {
String get name => 'web_release_bundle';
@override
- List get dependencies => [...compileTargets, templatedFilesTarget];
+ List get dependencies => [
+ ...compileTargets,
+ templatedFilesTarget,
+ const DartBuild(specifiedTargetPlatform: TargetPlatform.web_javascript),
+ ];
Iterable get buildPatternStems =>
compileTargets.expand((Dart2WebTarget target) => target.buildPatternStems);
@@ -448,9 +453,11 @@ class WebReleaseBundle extends Target {
final Directory outputDirectory = environment.outputDir.childDirectory('assets');
outputDirectory.createSync(recursive: true);
+ final DartHookResult dartHookResult = await DartBuild.loadHookResult(environment);
final Depfile depfile = await copyAssets(
environment,
environment.outputDir.childDirectory('assets'),
+ dartHookResult: dartHookResult,
targetPlatform: TargetPlatform.web_javascript,
buildMode: buildMode,
);
diff --git a/packages/flutter_tools/lib/src/build_system/targets/windows.dart b/packages/flutter_tools/lib/src/build_system/targets/windows.dart
index 807fcd7c90055..bc0394e81b05f 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/windows.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/windows.dart
@@ -106,6 +106,7 @@ abstract class BundleWindowsAssets extends Target {
@override
List get dependencies => [
+ const DartBuildForNative(),
const KernelSnapshot(),
const InstallCodeAssets(),
UnpackWindows(targetPlatform),
@@ -141,9 +142,11 @@ abstract class BundleWindowsAssets extends Target {
.childFile('app.dill')
.copySync(outputDirectory.childFile('kernel_blob.bin').path);
}
+ final DartHookResult dartHookResult = await DartBuild.loadHookResult(environment);
final Depfile depfile = await copyAssets(
environment,
outputDirectory,
+ dartHookResult: dartHookResult,
targetPlatform: targetPlatform,
buildMode: buildMode,
additionalContent: {
diff --git a/packages/flutter_tools/lib/src/bundle_builder.dart b/packages/flutter_tools/lib/src/bundle_builder.dart
index 22c455f5a2f13..6dd1160cc4b4e 100644
--- a/packages/flutter_tools/lib/src/bundle_builder.dart
+++ b/packages/flutter_tools/lib/src/bundle_builder.dart
@@ -29,10 +29,6 @@ class BundleBuilder {
///
/// The default `mainPath` is `lib/main.dart`.
/// The default `manifestPath` is `pubspec.yaml`.
- ///
- /// If [buildNativeAssets], native assets are built and the mapping for native
- /// assets lookup at runtime is embedded in the kernel file, otherwise an
- /// empty native assets mapping is embedded in the kernel file.
Future build({
required TargetPlatform platform,
required BuildInfo buildInfo,
@@ -42,7 +38,6 @@ class BundleBuilder {
String? applicationKernelFilePath,
String? depfilePath,
String? assetDirPath,
- bool buildNativeAssets = true,
@visibleForTesting BuildSystem? buildSystem,
}) async {
project ??= FlutterProject.current();
@@ -67,7 +62,6 @@ class BundleBuilder {
kTargetFile: mainPath,
kDeferredComponents: 'false',
...buildInfo.toBuildSystemEnvironment(),
- if (!buildNativeAssets) kNativeAssets: 'false',
},
artifacts: globals.artifacts!,
fileSystem: globals.fs,
diff --git a/packages/flutter_tools/lib/src/commands/build_bundle.dart b/packages/flutter_tools/lib/src/commands/build_bundle.dart
index ec06e8dcbeb61..262878ca00c2a 100644
--- a/packages/flutter_tools/lib/src/commands/build_bundle.dart
+++ b/packages/flutter_tools/lib/src/commands/build_bundle.dart
@@ -147,7 +147,6 @@ class BuildBundleCommand extends BuildSubCommand {
mainPath: targetFile,
depfilePath: stringArg('depfile'),
assetDirPath: stringArg('asset-dir'),
- buildNativeAssets: false,
);
return FlutterCommandResult.success();
}
diff --git a/packages/flutter_tools/lib/src/compile.dart b/packages/flutter_tools/lib/src/compile.dart
index dbf60e73a6e4d..3acc744124f6d 100644
--- a/packages/flutter_tools/lib/src/compile.dart
+++ b/packages/flutter_tools/lib/src/compile.dart
@@ -254,6 +254,7 @@ class KernelCompiler {
required List dartDefines,
required PackageConfig packageConfig,
String? nativeAssets,
+ String? recordedUsages,
}) async {
final TargetPlatform? platform =
targetModel == TargetModel.dartdevc ? TargetPlatform.web_javascript : null;
@@ -369,6 +370,7 @@ class KernelCompiler {
'--verbosity=error',
...?extraFrontEndOptions,
if (mainUri != null) mainUri else '--native-assets-only',
+ if (recordedUsages != null) '--recorded-usages=$recordedUsages',
];
_logger.printTrace(command.join(' '));
diff --git a/packages/flutter_tools/lib/src/features.dart b/packages/flutter_tools/lib/src/features.dart
index 3b9b21cfe0b96..9c14c15427aa6 100644
--- a/packages/flutter_tools/lib/src/features.dart
+++ b/packages/flutter_tools/lib/src/features.dart
@@ -48,6 +48,9 @@ abstract class FeatureFlags {
/// Whether native assets compilation and bundling is enabled.
bool get isNativeAssetsEnabled => false;
+ /// Whether dart data assets building and bundling is enabled.
+ bool get isDartDataAssetsEnabled => false;
+
/// Whether Swift Package Manager dependency management is enabled.
bool get isSwiftPackageManagerEnabled => false;
@@ -72,6 +75,7 @@ const List allFeatures = [
flutterCustomDevicesFeature,
cliAnimation,
nativeAssets,
+ dartDataAssets,
swiftPackageManager,
explicitPackageDependencies,
];
@@ -155,6 +159,14 @@ const Feature nativeAssets = Feature(
master: FeatureChannelSetting(available: true),
);
+/// Enable dart data assets building and bundling.
+const Feature dartDataAssets = Feature(
+ name: 'dart data assets building and bundling',
+ configSetting: 'enable-dart-data-assets',
+ environmentOverride: 'FLUTTER_DART_DATA_ASSETS',
+ master: FeatureChannelSetting(available: true),
+);
+
/// Enable Swift Package Manager as a darwin dependency manager.
const Feature swiftPackageManager = Feature(
name: 'support for Swift Package Manager for iOS and macOS',
diff --git a/packages/flutter_tools/lib/src/flutter_features.dart b/packages/flutter_tools/lib/src/flutter_features.dart
index ff83f64008945..527a4f36bdca4 100644
--- a/packages/flutter_tools/lib/src/flutter_features.dart
+++ b/packages/flutter_tools/lib/src/flutter_features.dart
@@ -55,6 +55,9 @@ class FlutterFeatureFlags implements FeatureFlags {
@override
bool get isNativeAssetsEnabled => isEnabled(nativeAssets);
+ @override
+ bool get isDartDataAssetsEnabled => isEnabled(dartDataAssets);
+
@override
bool get isSwiftPackageManagerEnabled => isEnabled(swiftPackageManager);
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 6a8d5f75406e5..625fecac8c7e3 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
@@ -4,17 +4,22 @@
// Logic for native assets shared between all host OSes.
+import 'package:icon_treeshaker/flutter_config.dart';
+import 'package:icon_treeshaker/hook_helper.dart' show FontAsset;
import 'package:logging/logging.dart' as logging;
import 'package:native_assets_builder/native_assets_builder.dart';
import 'package:native_assets_cli/code_assets_builder.dart';
+import 'package:native_assets_cli/data_assets_builder.dart';
import 'package:package_config/package_config_types.dart';
+import '../../artifacts.dart' show Artifact;
import '../../base/common.dart';
import '../../base/file_system.dart';
import '../../base/logger.dart';
import '../../base/platform.dart';
import '../../build_info.dart';
import '../../build_system/exceptions.dart';
+import '../../build_system/targets/common.dart';
import '../../cache.dart';
import '../../convert.dart';
import '../../features.dart';
@@ -27,53 +32,120 @@ import 'macos/native_assets.dart';
import 'macos/native_assets_host.dart';
import 'windows/native_assets.dart';
-/// The assets produced by a Dart build and the dependencies of those assets.
+export 'package:native_assets_cli/code_assets_builder.dart' show CodeAsset, DynamicLoadingBundled;
+export 'package:native_assets_cli/data_assets_builder.dart' show DataAsset;
+
+/// The assets produced by a Dart hook run and the dependencies of those assets.
///
/// If any of the dependencies change, then the Dart build should be performed
/// again.
-final class DartBuildResult {
- const DartBuildResult(this.codeAssets, this.dependencies);
-
- const DartBuildResult.empty() : codeAssets = const [], dependencies = const [];
+final class DartHookResult {
+ const DartHookResult(
+ this.buildStart,
+ this.buildEnd,
+ this.codeAssets,
+ this.dataAssets,
+ this.fontAssets,
+ this.dependencies,
+ );
- factory DartBuildResult.fromJson(Map json) {
+ DartHookResult.empty()
+ : buildStart = DateTime.now(),
+ buildEnd = DateTime.now(),
+ codeAssets = const [],
+ dataAssets = const [],
+ fontAssets = const [],
+ dependencies = const [];
+
+ factory DartHookResult.fromJson(Map json) {
+ final DateTime buildStart = DateTime.parse((json['build_start'] as String?)!);
+ final DateTime buildEnd = DateTime.parse((json['build_end'] as String?)!);
final List dependencies = [
for (final Object? encodedUri in json['dependencies']! as List