From 4d7857112b0c84283e6c6e05704484404e65e85f Mon Sep 17 00:00:00 2001 From: Moritz Date: Tue, 4 Mar 2025 09:38:11 +0100 Subject: [PATCH 01/25] Add experimental data assets --- packages/flutter_tools/lib/src/asset.dart | 53 ++- .../flutter_tools/lib/src/build_info.dart | 14 - .../lib/src/build_system/targets/android.dart | 8 +- .../lib/src/build_system/targets/assets.dart | 10 +- .../lib/src/build_system/targets/common.dart | 8 +- .../lib/src/build_system/targets/ios.dart | 8 +- .../lib/src/build_system/targets/linux.dart | 3 + .../lib/src/build_system/targets/macos.dart | 25 +- .../build_system/targets/native_assets.dart | 89 +++-- .../lib/src/build_system/targets/web.dart | 9 +- .../lib/src/build_system/targets/windows.dart | 3 + .../flutter_tools/lib/src/bundle_builder.dart | 6 - .../lib/src/commands/build_bundle.dart | 1 - packages/flutter_tools/lib/src/features.dart | 12 + .../lib/src/flutter_features.dart | 3 + .../isolated/native_assets/native_assets.dart | 262 +++++++++---- .../native_assets/test/native_assets.dart | 2 +- .../lib/src/isolated/resident_web_runner.dart | 1 + .../lib/src/resident_runner.dart | 78 ++-- packages/flutter_tools/lib/src/run_hot.dart | 39 +- .../permeable/build_bundle_test.dart | 8 - .../test/general.shard/devfs_test.dart | 2 + .../targets/native_assets_test.dart | 8 - .../isolated/native_assets_test.dart | 6 +- .../isolated/dart_data_asset_test.dart | 361 ++++++++++++++++++ .../transition_test_utils.dart | 36 +- packages/flutter_tools/test/src/fakes.dart | 4 + 27 files changed, 822 insertions(+), 237 deletions(-) create mode 100644 packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_test.dart diff --git a/packages/flutter_tools/lib/src/asset.dart b/packages/flutter_tools/lib/src/asset.dart index 138eb2725a20e..1f0ab433c0b64 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({ + DartBuildResult? dartBuildResult, String manifestPath = defaultManifestPath, required String packageConfigPath, bool deferredComponentsEnabled = false, @@ -187,6 +189,8 @@ class ManifestAssetBundle implements AssetBundle { DateTime? _lastBuildTimestamp; + DartBuildResult? _lastDartBuildResult; + // 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() || + _lastDartBuildResult == null || + // We need to re-run the dart build. + !_lastDartBuildResult!.isBuildUpToDate(_fileSystem) || + // We don't have to re-run the dart build, but some files the dart build + // wants us to bundle have changed contents. + _lastDartBuildResult!.isBuildOutputDirty(_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({ + DartBuildResult? dartBuildResult, 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(); + _lastDartBuildResult = dartBuildResult; if (flutterManifest.isEmpty) { entries[_kAssetManifestJsonFilename] = AssetBundleEntry( DevFSStringContent('{}'), @@ -400,8 +414,33 @@ class ManifestAssetBundle implements AssetBundle { } } + for (final DataAsset dataAsset in dartBuildResult?.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, + ); + 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..66aaec6c97ec9 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 DartBuildResult dartBuildResult = await DartBuild.loadBuildResult(environment); final Depfile assetDepfile = await copyAssets( environment, outputDirectory, + dartBuildResult: dartBuildResult, 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..b3424c9024a5b 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 DartBuildResult dartBuildResult, 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( + dartBuildResult: dartBuildResult, 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 DartBuildResult dartBuildResult = await DartBuild.loadBuildResult(environment); final Depfile depfile = await copyAssets( environment, output, + dartBuildResult: dartBuildResult, 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..d5f3902d321fd 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 DartBuildResult dartBuildResult = await DartBuild.loadBuildResult(environment); final Depfile assetDepfile = await copyAssets( environment, environment.outputDir, + dartBuildResult: dartBuildResult, 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. 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..987408e6f4b0b 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 DartBuildResult dartBuildResult = await DartBuild.loadBuildResult(environment); final Depfile assetDepfile = await copyAssets( environment, assetDirectory, + dartBuildResult: dartBuildResult, 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..a8c5f81946072 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 DartBuildResult dartBuildResult = await DartBuild.loadBuildResult(environment); final Depfile depfile = await copyAssets( environment, outputDirectory, + dartBuildResult: dartBuildResult, 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..022f6f6283695 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 DartBuildResult dartBuildResult = await DartBuild.loadBuildResult(environment); final Depfile assetDepfile = await copyAssets( environment, assetDirectory, + dartBuildResult: dartBuildResult, 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..b26f499171a37 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 @@ -13,51 +13,53 @@ import '../../dart/package_map.dart'; import '../../isolated/native_assets/native_assets.dart'; import '../build_system.dart'; import '../depfile.dart'; -import '../exceptions.dart'; import 'common.dart'; +export '../../isolated/native_assets/native_assets.dart' + show CodeAsset, DartBuildResult, 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 TargetPlatform? targetPlatform = + specifiedTargetPlatform ?? _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 File dartBuildResultJsonFile = environment.buildDir.childFile(dartBuildResultFilename); if (!dartBuildResultJsonFile.parent.existsSync()) { @@ -106,11 +108,17 @@ abstract class DartBuild extends Target { final File dartBuildResultJsonFile = environment.buildDir.childFile( DartBuild.dartBuildResultFilename, ); + if (!dartBuildResultJsonFile.existsSync()) { + return DartBuildResult.empty(); + } return DartBuildResult.fromJson( json.decode(dartBuildResultJsonFile.readAsStringSync()) as Map, ); } + @override + List get dependencies => []; + static const String dartBuildResultFilename = 'dart_build_result.json'; static const String depFilename = 'dart_build.d'; } @@ -134,7 +142,7 @@ class InstallCodeAssets extends Target { Future build(Environment environment) async { final Uri projectUri = environment.projectDir.uri; final FileSystem fileSystem = environment.fileSystem; - final TargetPlatform targetPlatform = _getTargetPlatformFromEnvironment(environment, name); + final TargetPlatform targetPlatform = _getTargetPlatformFromEnvironment(environment, name)!; // We fetch the result from the [DartBuild]. final DartBuildResult dartBuildResult = await DartBuild.loadBuildResult(environment); @@ -188,10 +196,9 @@ class InstallCodeAssets extends Target { static const String depFilename = 'install_code_assets.d'; } -TargetPlatform _getTargetPlatformFromEnvironment(Environment environment, String name) { +TargetPlatform? _getTargetPlatformFromEnvironment(Environment environment, String name) { final String? targetPlatformEnvironment = environment.defines[kTargetPlatform]; - if (targetPlatformEnvironment == null) { - throw MissingDefineException(kTargetPlatform, name); - } - return getTargetPlatformForName(targetPlatformEnvironment); + return targetPlatformEnvironment != null + ? getTargetPlatformForName(targetPlatformEnvironment) + : null; } 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..a1c1da51b54ad 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 DartBuildResult dartBuildResult = await DartBuild.loadBuildResult(environment); final Depfile depfile = await copyAssets( environment, environment.outputDir.childDirectory('assets'), + dartBuildResult: dartBuildResult, 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..a94e4f521cef1 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 DartBuildResult dartBuildResult = await DartBuild.loadBuildResult(environment); final Depfile depfile = await copyAssets( environment, outputDirectory, + dartBuildResult: dartBuildResult, 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/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..186a2f114d2d5 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 @@ -7,6 +7,7 @@ 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 '../../base/common.dart'; @@ -27,40 +28,92 @@ import 'macos/native_assets.dart'; import 'macos/native_assets_host.dart'; import 'windows/native_assets.dart'; +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 build 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( + this.buildStart, + this.buildEnd, + this.codeAssets, + this.dataAssets, + this.dependencies, + ); - const DartBuildResult.empty() : codeAssets = const [], dependencies = const []; + DartBuildResult.empty() + : buildStart = DateTime.now(), + buildEnd = DateTime.now(), + codeAssets = const [], + dataAssets = const [], + dependencies = const []; factory DartBuildResult.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) Uri.parse(encodedUri! as String), ]; final List codeAssets = [ - for (final Object? json in json['code_assets']! as List) + for (final Object? json in json['code_assets'] as List? ?? const []) CodeAsset.fromEncoded(EncodedAsset.fromJson(json! as Map)), ]; - return DartBuildResult(codeAssets, dependencies); + final List dataAssets = [ + for (final Object? json in json['data_assets'] as List? ?? const []) + DataAsset.fromEncoded(EncodedAsset.fromJson(json! as Map)), + ]; + return DartBuildResult(buildStart, buildEnd, codeAssets, dataAssets, dependencies); } + final DateTime buildStart; + final DateTime buildEnd; final List codeAssets; + final List dataAssets; final List dependencies; Map toJson() => { + 'build_start': buildStart.toIso8601String(), + 'build_end': buildEnd.toIso8601String(), 'dependencies': [for (final Uri dep in dependencies) dep.toString()], 'code_assets': [for (final CodeAsset code in codeAssets) code.encode().toJson()], + 'data_assets': [for (final DataAsset asset in dataAssets) asset.encode().toJson()], }; /// The files that eventually should be bundled with the app. List get filesToBeBundled => [ for (final CodeAsset code in codeAssets) if (code.linkMode is DynamicLoadingBundled) code.file!, + for (final DataAsset asset in dataAssets) asset.file, ]; + + /// Whether caller may need to re-run the dart build. + bool isBuildUpToDate(FileSystem fileSystem) { + return !_wasAnyFileModifiedSince(fileSystem, buildStart, dependencies); + } + + /// Whether the files produced by the build are up-to-date. + /// + /// NOTICE: The build itself may be up-to-date but the output may not be (as + /// the output may be existing on disc and not be produced by the build + /// itself - in which case we may not need to re-build if the file changes, + /// but we may need to make a new asset bundle with the modified file). + bool isBuildOutputDirty(FileSystem fileSystem) { + return _wasAnyFileModifiedSince(fileSystem, buildEnd, filesToBeBundled); + } + + static bool _wasAnyFileModifiedSince(FileSystem fileSystem, DateTime since, List uris) { + for (final Uri uri in uris) { + final DateTime modified = fileSystem.statSync(uri.toFilePath()).modified; + if (modified.isAfter(since)) { + return true; + } + } + return false; + } } /// Invokes the build of all transitive Dart packages and prepares code assets @@ -68,12 +121,20 @@ final class DartBuildResult { Future runFlutterSpecificDartBuild({ required Map environmentDefines, required FlutterNativeAssetsBuildRunner buildRunner, - required TargetPlatform targetPlatform, + required TargetPlatform? targetPlatform, required Uri projectUri, required FileSystem fileSystem, }) async { - final OS targetOS = getNativeOSFromTargetPlatform(targetPlatform); + final bool isWeb = targetPlatform == TargetPlatform.web_javascript; + final OS? targetOS = + targetPlatform != null ? getNativeOSFromTargetPlatform(targetPlatform) : null; + assert(featureFlags.isNativeAssetsEnabled || targetOS != null); final Uri buildUri = nativeAssetsBuildUri(projectUri, targetOS); + + // Sanity check. + final String? codesignIdentity = environmentDefines[kCodesignIdentity]; + assert(codesignIdentity == null || targetOS == OS.iOS || targetOS == OS.macOS); + final Directory buildDir = fileSystem.directory(buildUri); final bool flutterTester = targetPlatform == TargetPlatform.tester; @@ -85,20 +146,24 @@ Future runFlutterSpecificDartBuild({ } if (!await _nativeBuildRequired(buildRunner)) { - return const DartBuildResult.empty(); + return DartBuildResult.empty(); } final BuildMode buildMode = _getBuildMode(environmentDefines, flutterTester); - final List architectures = - flutterTester - ? [Architecture.current] - : _architecturesForOS(targetPlatform, targetOS, environmentDefines); + final List? architectures = + isWeb || targetPlatform == null + ? null + : (flutterTester + ? [Architecture.current] + : _architecturesForOS(targetPlatform, targetOS!, environmentDefines)); final DartBuildResult result = - architectures.isEmpty - ? const DartBuildResult.empty() + architectures?.isEmpty ?? false + ? DartBuildResult.empty() : await _runDartBuild( environmentDefines: environmentDefines, buildRunner: buildRunner, + codeAssetSupport: !isWeb && featureFlags.isNativeAssetsEnabled, + dataAssetSupport: featureFlags.isDartDataAssetsEnabled, architectures: architectures, projectUri: projectUri, linkingEnabled: _nativeAssetsLinkingEnabled(buildMode), @@ -116,14 +181,16 @@ Future installCodeAssets({ required FileSystem fileSystem, required Uri nativeAssetsFileUri, }) async { - final OS targetOS = getNativeOSFromTargetPlatform(targetPlatform); + final OS? targetOS = getNativeOSFromTargetPlatform(targetPlatform); + assert(targetOS != null); + final Uri buildUri = nativeAssetsBuildUri(projectUri, targetOS); final bool flutterTester = targetPlatform == TargetPlatform.tester; final BuildMode buildMode = _getBuildMode(environmentDefines, flutterTester); final String? codesignIdentity = environmentDefines[kCodesignIdentity]; final Map assetTargetLocations = assetTargetLocationsForOS( - targetOS, + targetOS!, dartBuildResult.codeAssets, flutterTester, buildUri, @@ -371,11 +438,12 @@ Future _nativeBuildRequired(FlutterNativeAssetsBuildRunner buildRunner) as return false; } - if (!featureFlags.isNativeAssetsEnabled) { + if (!featureFlags.isNativeAssetsEnabled && !featureFlags.isDartDataAssetsEnabled) { final String packageNames = packagesWithNativeAssets.join(' '); throwToolExit( - 'Package(s) $packageNames require the native assets feature to be enabled. ' - 'Enable using `flutter config --enable-native-assets`.', + 'Package(s) $packageNames require the dart assets feature to be enabled.\n' + ' Enable code assets using `flutter config --enable-native-assets`.' + ' Enable data assets using `flutter config --enable-dart-data-assets`.', ); } return true; @@ -408,9 +476,9 @@ Future ensureNoNativeAssetsOrOsIsSupported( /// This should be the same for different archs, debug/release, etc. /// It should work for all macOS. -Uri nativeAssetsBuildUri(Uri projectUri, OS os) { +Uri nativeAssetsBuildUri(Uri projectUri, OS? os) { final String buildDir = getBuildDirectory(); - return projectUri.resolve('$buildDir/native_assets/$os/'); + return projectUri.resolve('$buildDir/native_assets/${os == null ? 'web' : os.name}/'); } Map _assetTargetLocationsWindowsLinux( @@ -555,18 +623,23 @@ Future _copyNativeCodeAssetsForOS( Future _runDartBuild({ required Map environmentDefines, required FlutterNativeAssetsBuildRunner buildRunner, - required List architectures, + required List? architectures, required Uri projectUri, required FileSystem fileSystem, required OS? targetOS, required bool linkingEnabled, + required bool codeAssetSupport, + required bool dataAssetSupport, }) async { - final String architectureString = - architectures.length == 1 - ? architectures.single.toString() - : architectures.toList().toString(); + final DateTime buildStart = DateTime.now(); + + // For native builds we have valid architectures. + // For web builds we use `null` as the single architecture. + final bool isWeb = architectures == null; + + final String targetString = isWeb ? 'web' : '$targetOS ${architectures.join(',')}'; - globals.logger.printTrace('Building native assets for $targetOS $architectureString.'); + globals.logger.printTrace('Building native assets for $targetString.'); final List assets = []; final Set dependencies = {}; @@ -599,30 +672,41 @@ Future _runDartBuild({ : null; final MacOSCodeConfig? macOSConfig = targetOS == OS.macOS ? MacOSCodeConfig(targetVersion: targetMacOSVersion) : null; - for (final Architecture architecture in architectures) { + for (final Architecture? architecture in architectures ?? [null]) { + assert(!(codeAssetSupport && architecture == null)); final BuildResult? buildResult = await buildRunner.build( - buildAssetTypes: [CodeAsset.type], - inputCreator: - () => - BuildInputBuilder() - ..config.setupCode( - targetArchitecture: architecture, - linkModePreference: LinkModePreference.dynamic, - cCompiler: cCompilerConfig, - targetOS: targetOS!, - android: androidConfig, - iOS: iosConfig, - macOS: macOSConfig, - ), + buildAssetTypes: [ + if (codeAssetSupport) CodeAsset.type, + if (dataAssetSupport) DataAsset.type, + ], + inputCreator: () { + final BuildInputBuilder buildInputBuilder = BuildInputBuilder(); + if (targetOS != null) { + buildInputBuilder.config.setupCode( + targetArchitecture: architecture, + linkModePreference: LinkModePreference.dynamic, + cCompiler: cCompilerConfig, + targetOS: targetOS, + android: androidConfig, + iOS: iosConfig, + macOS: macOSConfig, + ); + } + return buildInputBuilder; + }, inputValidator: - (BuildInput config) async => [...await validateCodeAssetBuildInput(config)], + (BuildInput config) async => [ + if (codeAssetSupport) ...await validateCodeAssetBuildInput(config), + if (dataAssetSupport) ...await validateDataAssetBuildInput(config), + ], buildValidator: (BuildInput config, BuildOutput output) async => [ - ...await validateCodeAssetBuildOutput(config, output), + if (codeAssetSupport) ...await validateCodeAssetBuildOutput(config, output), + if (dataAssetSupport) ...await validateDataAssetBuildOutput(config, output), ], applicationAssetValidator: (List assets) async => [ - ...await validateCodeAssetInApplication(assets), + if (codeAssetSupport) ...await validateCodeAssetInApplication(assets), ], workingDirectory: projectUri, linkingEnabled: linkingEnabled, @@ -633,40 +717,50 @@ Future _runDartBuild({ dependencies.addAll(buildResult.dependencies); if (!linkingEnabled) { assets.addAll(buildResult.encodedAssets); - } else { - final LinkResult? linkResult = await buildRunner.link( - buildAssetTypes: [CodeAsset.type], - inputCreator: - () => - LinkInputBuilder() - ..config.setupCode( - targetArchitecture: architecture, - linkModePreference: LinkModePreference.dynamic, - cCompiler: cCompilerConfig, - targetOS: targetOS!, - android: androidConfig, - iOS: iosConfig, - macOS: macOSConfig, - ), - inputValidator: - (LinkInput config) async => [...await validateCodeAssetLinkInput(config)], - linkValidator: - (LinkInput config, LinkOutput output) async => [ - ...await validateCodeAssetLinkOutput(config, output), - ], - applicationAssetValidator: - (List assets) async => [ - ...await validateCodeAssetInApplication(assets), - ], - workingDirectory: projectUri, - buildResult: buildResult, - ); - if (linkResult == null) { - _throwNativeAssetsLinkFailed(); - } - assets.addAll(linkResult.encodedAssets); - dependencies.addAll(linkResult.dependencies); + continue; + } + final LinkResult? linkResult = await buildRunner.link( + buildAssetTypes: [ + if (codeAssetSupport) CodeAsset.type, + if (dataAssetSupport) DataAsset.type, + ], + inputCreator: () { + final LinkInputBuilder linkInputBuilder = LinkInputBuilder(); + if (targetOS != null) { + linkInputBuilder.config.setupCode( + targetArchitecture: architecture, + linkModePreference: LinkModePreference.dynamic, + cCompiler: cCompilerConfig, + targetOS: targetOS, + android: androidConfig, + iOS: iosConfig, + macOS: macOSConfig, + ); + } + return linkInputBuilder; + }, + inputValidator: + (LinkInput config) async => [ + if (codeAssetSupport) ...await validateCodeAssetLinkInput(config), + if (dataAssetSupport) ...await validateDataAssetLinkInput(config), + ], + linkValidator: + (LinkInput config, LinkOutput output) async => [ + if (codeAssetSupport) ...await validateCodeAssetLinkOutput(config, output), + if (dataAssetSupport) ...await validateDataAssetLinkOutput(config, output), + ], + applicationAssetValidator: + (List assets) async => [ + if (codeAssetSupport) ...await validateCodeAssetInApplication(assets), + ], + workingDirectory: projectUri, + buildResult: buildResult, + ); + if (linkResult == null) { + _throwNativeAssetsLinkFailed(); } + assets.addAll(linkResult.encodedAssets); + dependencies.addAll(linkResult.dependencies); } final List codeAssets = @@ -674,8 +768,15 @@ Future _runDartBuild({ .where((EncodedAsset asset) => asset.type == CodeAsset.type) .map(CodeAsset.fromEncoded) .toList(); - globals.logger.printTrace('Building native assets for $targetOS $architectureString done.'); - return DartBuildResult(codeAssets, dependencies.toList()); + final List dataAssets = + assets + .where((EncodedAsset asset) => asset.type == DataAsset.type) + .map(DataAsset.fromEncoded) + .toList(); + globals.logger.printTrace('Building native assets for $targetString done.'); + + final DateTime buildEnd = DateTime.now(); + return DartBuildResult(buildStart, buildEnd, codeAssets, dataAssets, dependencies.toList()); } List _architecturesForOS( @@ -763,7 +864,8 @@ Never _throwNativeAssetsLinkFailed() { throwToolExit('Linking native assets failed. See the logs for more details.'); } -OS getNativeOSFromTargetPlatform(TargetPlatform platform) { +/// Returns null for the web, and non-null otherwise. +OS? getNativeOSFromTargetPlatform(TargetPlatform platform) { switch (platform) { case TargetPlatform.ios: return OS.iOS; @@ -795,7 +897,7 @@ OS getNativeOSFromTargetPlatform(TargetPlatform platform) { throw StateError('Unknown operating system'); } case TargetPlatform.web_javascript: - throw StateError('No dart builds for web yet.'); + return null; } } diff --git a/packages/flutter_tools/lib/src/isolated/native_assets/test/native_assets.dart b/packages/flutter_tools/lib/src/isolated/native_assets/test/native_assets.dart index 832492b68474b..ccefb5e596405 100644 --- a/packages/flutter_tools/lib/src/isolated/native_assets/test/native_assets.dart +++ b/packages/flutter_tools/lib/src/isolated/native_assets/test/native_assets.dart @@ -53,7 +53,7 @@ Future testCompilerBuildNativeAssets(BuildInfo buildInfo) async { // Only `flutter test` uses the // `build/native_assets//native_assets.json` file which uses absolute // paths to the shared libraries. - final OS targetOS = getNativeOSFromTargetPlatform(TargetPlatform.tester); + final OS? targetOS = getNativeOSFromTargetPlatform(TargetPlatform.tester); final Uri buildUri = nativeAssetsBuildUri(projectUri, targetOS); final Uri nativeAssetsFileUri = buildUri.resolve('native_assets.json'); diff --git a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart index 48075324ca19f..556600147bb24 100644 --- a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart +++ b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart @@ -643,6 +643,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). if (rebuildBundle) { _logger.printTrace('Updating assets'); final int result = await assetBundle.build( + dartBuildResult: await runDartBuild(targetPlatform: TargetPlatform.web_javascript), packageConfigPath: debuggingOptions.buildInfo.packageConfigPath, targetPlatform: TargetPlatform.web_javascript, ); diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index 22b32458208d0..cfeb8acfa5fd1 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -24,6 +24,7 @@ import 'base/terminal.dart'; import 'base/utils.dart'; import 'build_info.dart'; import 'build_system/build_system.dart'; +import 'build_system/targets/native_assets.dart'; import 'build_system/tools/shader_compiler.dart'; import 'bundle.dart'; import 'cache.dart'; @@ -1050,7 +1051,34 @@ abstract class ResidentRunner extends ResidentHandlers { bool _exited = false; Completer _finished = Completer(); BuildResult? _lastBuild; - Environment? _environment; + + late final Environment _environment = Environment( + artifacts: globals.artifacts!, + logger: globals.logger, + cacheDir: globals.cache.getRoot(), + engineVersion: globals.flutterVersion.engineRevision, + fileSystem: globals.fs, + flutterRootDir: globals.fs.directory(Cache.flutterRoot), + outputDir: globals.fs.directory(getBuildDirectory()), + processManager: globals.processManager, + platform: globals.platform, + analytics: globals.analytics, + projectDir: globals.fs.currentDirectory, + packageConfigPath: debuggingOptions.buildInfo.packageConfigPath, + generateDartPluginRegistry: generateDartPluginRegistry, + defines: { + // Needed for Dart plugin registry generation. + kTargetFile: mainPath, + kBuildMode: debuggingOptions.buildInfo.mode.cliName, + }, + ); + + /// The result of the last dart build. Will be populated in + /// [runDartBuild]. + DartBuildResult? _dartBuildResult; + + /// The last dart build's result. + DartBuildResult? get dartBuildResult => _dartBuildResult; @override bool hotMode; @@ -1148,26 +1176,6 @@ abstract class ResidentRunner extends ResidentHandlers { @override Future runSourceGenerators() async { - _environment ??= Environment( - artifacts: globals.artifacts!, - logger: globals.logger, - cacheDir: globals.cache.getRoot(), - engineVersion: globals.flutterVersion.engineRevision, - fileSystem: globals.fs, - flutterRootDir: globals.fs.directory(Cache.flutterRoot), - outputDir: globals.fs.directory(getBuildDirectory()), - processManager: globals.processManager, - platform: globals.platform, - analytics: globals.analytics, - projectDir: globals.fs.currentDirectory, - packageConfigPath: debuggingOptions.buildInfo.packageConfigPath, - generateDartPluginRegistry: generateDartPluginRegistry, - defines: { - // Needed for Dart plugin registry generation. - kTargetFile: mainPath, - }, - ); - final CompositeTarget compositeTarget = CompositeTarget([ globals.buildTargets.generateLocalizationsTarget, globals.buildTargets.dartPluginRegistrantTarget, @@ -1175,7 +1183,7 @@ abstract class ResidentRunner extends ResidentHandlers { _lastBuild = await globals.buildSystem.buildIncremental( compositeTarget, - _environment!, + _environment, _lastBuild, ); if (!_lastBuild!.success) { @@ -1189,6 +1197,32 @@ abstract class ResidentRunner extends ResidentHandlers { globals.printTrace('complete'); } + Future runDartBuild({required TargetPlatform targetPlatform}) async { + globals.printTrace('runDartBuild() with ${_environment.defines} and $targetPlatform'); + if (_dartBuildResult?.isBuildUpToDate(globals.fs) ?? false) { + globals.printTrace('runDartBuild() - up-to-date already'); + return _dartBuildResult!; + } + globals.printTrace('runDartBuild() - will perform dart build'); + + final BuildResult lastBuild = await globals.buildSystem.build( + DartBuild(specifiedTargetPlatform: targetPlatform), + _environment, + ); + if (!lastBuild.success) { + for (final ExceptionMeasurement exceptionMeasurement in lastBuild.exceptions.values) { + globals.printError( + exceptionMeasurement.exception.toString(), + stackTrace: globals.logger.isVerbose ? exceptionMeasurement.stackTrace : null, + ); + } + } + + _dartBuildResult = await DartBuild.loadBuildResult(_environment); + globals.printTrace('runDartBuild() - done'); + return _dartBuildResult!; + } + @protected void writeVmServiceFile() { if (debuggingOptions.vmserviceOutFile != null) { diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart index 8e26c310875de..f347dca11bf97 100644 --- a/packages/flutter_tools/lib/src/run_hot.dart +++ b/packages/flutter_tools/lib/src/run_hot.dart @@ -125,7 +125,13 @@ class HotRunner extends ResidentRunner { final Map> benchmarkData = >{}; - String? _targetPlatform; + String? _targetPlatformName; + TargetPlatform get _targetPlatform => + _targetPlatformName != null + ? getTargetPlatformForName(_targetPlatformName!) + : throw ArgumentError( + 'Access to the target platform needs a call to _calculateTargetPlatform first', + ); String? _sdkName; bool? _emulator; @@ -137,22 +143,22 @@ class HotRunner extends ResidentRunner { bool get supportsDetach => stopAppDuringCleanup; Future _calculateTargetPlatform() async { - if (_targetPlatform != null) { + if (_targetPlatformName != null) { return; } switch (flutterDevices.length) { case 1: final Device device = flutterDevices.first.device!; - _targetPlatform = getNameForTargetPlatform(await device.targetPlatform); + _targetPlatformName = getNameForTargetPlatform(await device.targetPlatform); _sdkName = await device.sdkNameAndVersion; _emulator = await device.isLocalEmulator; case > 1: - _targetPlatform = 'multiple'; + _targetPlatformName = 'multiple'; _sdkName = 'multiple'; _emulator = false; default: - _targetPlatform = 'unknown'; + _targetPlatformName = 'unknown'; _sdkName = 'unknown'; _emulator = false; } @@ -304,7 +310,10 @@ class HotRunner extends ResidentRunner { } final Stopwatch initialUpdateDevFSsTimer = Stopwatch()..start(); - final UpdateFSReport devfsResult = await _updateDevFS(fullRestart: needsFullRestart); + final UpdateFSReport devfsResult = await _updateDevFS( + fullRestart: needsFullRestart, + targetPlatform: _targetPlatform, + ); _addBenchmarkData( 'hotReloadInitialDevFSSyncMilliseconds', initialUpdateDevFSsTimer.elapsed.inMilliseconds, @@ -437,7 +446,7 @@ class HotRunner extends ResidentRunner { appStartedCompleter?.future.then((_) { HotEvent( 'reload-ready', - targetPlatform: _targetPlatform!, + targetPlatform: _targetPlatformName!, sdkName: _sdkName!, emulator: _emulator!, fullRestart: false, @@ -449,7 +458,7 @@ class HotRunner extends ResidentRunner { _analytics.send( Event.hotRunnerInfo( label: 'reload-ready', - targetPlatform: _targetPlatform!, + targetPlatform: _targetPlatformName!, sdkName: _sdkName!, emulator: _emulator!, fullRestart: false, @@ -489,12 +498,16 @@ class HotRunner extends ResidentRunner { ]; } - Future _updateDevFS({bool fullRestart = false}) async { + Future _updateDevFS({ + bool fullRestart = false, + required TargetPlatform targetPlatform, + }) async { final bool isFirstUpload = !assetBundle.wasBuiltOnce(); final bool rebuildBundle = assetBundle.needsBuild(); if (rebuildBundle) { globals.printTrace('Updating assets'); final int result = await assetBundle.build( + dartBuildResult: await runDartBuild(targetPlatform: targetPlatform), packageConfigPath: debuggingOptions.buildInfo.packageConfigPath, flavor: debuggingOptions.buildInfo.flavor, ); @@ -609,7 +622,7 @@ class HotRunner extends ResidentRunner { final Stopwatch restartTimer = Stopwatch()..start(); UpdateFSReport updatedDevFS; try { - updatedDevFS = await _updateDevFS(fullRestart: true); + updatedDevFS = await _updateDevFS(fullRestart: true, targetPlatform: _targetPlatform); } finally { hotRunnerConfig!.updateDevFSComplete(); } @@ -777,7 +790,7 @@ class HotRunner extends ResidentRunner { if (fullRestart) { final OperationResult result = await _fullRestartHelper( - targetPlatform: _targetPlatform, + targetPlatform: _targetPlatformName, sdkName: _sdkName, emulator: _emulator, reason: reason, @@ -794,7 +807,7 @@ class HotRunner extends ResidentRunner { return result; } final OperationResult result = await _hotReloadHelper( - targetPlatform: _targetPlatform, + targetPlatform: _targetPlatformName, sdkName: _sdkName, emulator: _emulator, reason: reason, @@ -1016,7 +1029,7 @@ class HotRunner extends ResidentRunner { final Stopwatch devFSTimer = Stopwatch()..start(); UpdateFSReport updatedDevFS; try { - updatedDevFS = await _updateDevFS(); + updatedDevFS = await _updateDevFS(targetPlatform: _targetPlatform); } finally { hotRunnerConfig!.updateDevFSComplete(); } diff --git a/packages/flutter_tools/test/commands.shard/permeable/build_bundle_test.dart b/packages/flutter_tools/test/commands.shard/permeable/build_bundle_test.dart index 12155e369892f..9a6114b9cb3e6 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/build_bundle_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/build_bundle_test.dart @@ -308,7 +308,6 @@ void main() { kIconTreeShakerFlag: 'false', kDeferredComponents: 'false', kDartObfuscation: 'false', - kNativeAssets: 'false', }); }), FileSystem: fsFactory, @@ -350,7 +349,6 @@ void main() { kIconTreeShakerFlag: 'false', kDeferredComponents: 'false', kDartObfuscation: 'false', - kNativeAssets: 'false', }); }), FileSystem: fsFactory, @@ -392,7 +390,6 @@ void main() { kIconTreeShakerFlag: 'false', kDeferredComponents: 'false', kDartObfuscation: 'false', - kNativeAssets: 'false', }); }), FileSystem: fsFactory, @@ -435,7 +432,6 @@ void main() { kIconTreeShakerFlag: 'false', kDeferredComponents: 'false', kDartObfuscation: 'false', - kNativeAssets: 'false', }); }), FileSystem: fsFactory, @@ -478,7 +474,6 @@ void main() { kIconTreeShakerFlag: 'false', kDeferredComponents: 'false', kDartObfuscation: 'false', - kNativeAssets: 'false', }); }), FileSystem: fsFactory, @@ -521,7 +516,6 @@ void main() { kIconTreeShakerFlag: 'false', kDeferredComponents: 'false', kDartObfuscation: 'false', - kNativeAssets: 'false', }); }), FileSystem: fsFactory, @@ -571,7 +565,6 @@ void main() { kIconTreeShakerFlag: 'false', kDeferredComponents: 'false', kDartObfuscation: 'false', - kNativeAssets: 'false', }); }), FileSystem: fsFactory, @@ -621,7 +614,6 @@ void main() { kIconTreeShakerFlag: 'false', kDeferredComponents: 'false', kDartObfuscation: 'false', - kNativeAssets: 'false', }); }), FileSystem: fsFactory, diff --git a/packages/flutter_tools/test/general.shard/devfs_test.dart b/packages/flutter_tools/test/general.shard/devfs_test.dart index b99e49d1ad800..2bfbe680c4199 100644 --- a/packages/flutter_tools/test/general.shard/devfs_test.dart +++ b/packages/flutter_tools/test/general.shard/devfs_test.dart @@ -24,6 +24,7 @@ import 'package:flutter_tools/src/build_system/tools/shader_compiler.dart'; import 'package:flutter_tools/src/compile.dart'; import 'package:flutter_tools/src/devfs.dart'; import 'package:flutter_tools/src/flutter_manifest.dart'; +import 'package:flutter_tools/src/isolated/native_assets/native_assets.dart'; import 'package:package_config/package_config.dart'; import 'package:test/fake.dart'; import 'package:vm_service/vm_service.dart' as vm_service; @@ -990,6 +991,7 @@ class FakeBundle extends AssetBundle { @override Future build({ + DartBuildResult? dartBuildResult, String manifestPath = defaultManifestPath, String? assetDirPath, String? packageConfigPath, diff --git a/packages/flutter_tools/test/general.shard/isolated/build_system/targets/native_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/build_system/targets/native_assets_test.dart index 5c2f6b6dda028..b9676354bf07d 100644 --- a/packages/flutter_tools/test/general.shard/isolated/build_system/targets/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/build_system/targets/native_assets_test.dart @@ -65,14 +65,6 @@ void main() { androidEnvironment.buildDir.createSync(recursive: true); }); - testWithoutContext('NativeAssets throws error if missing target platform', () async { - iosEnvironment.defines.remove(kTargetPlatform); - expect( - const DartBuildForNative().build(iosEnvironment), - throwsA(isA()), - ); - }); - testUsingContext('NativeAssets defaults to ios archs if missing', () async { writePackageConfigFile(directory: iosEnvironment.projectDir, mainLibName: 'my_app'); 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 b14befe960bd2..b2f61ed57cbde 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 @@ -117,11 +117,7 @@ void main() { packagesWithNativeAssetsResult: ['bar'], ), ), - throwsToolExit( - message: - 'Package(s) bar require the native assets feature to be enabled. ' - 'Enable using `flutter config --enable-native-assets`.', - ), + throwsToolExit(message: 'Enable code assets using `flutter config --enable-native-assets`'), ); }, ); diff --git a/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_test.dart b/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_test.dart new file mode 100644 index 0000000000000..0c6972507f456 --- /dev/null +++ b/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_test.dart @@ -0,0 +1,361 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This test exercises the embedding of the native assets mapping in dill files. +// An initial dill file is created by `flutter assemble` and used for running +// the application. This dill must contain the mapping. +// When doing hot reload, this mapping must stay in place. +// When doing a hot restart, a new dill file is pushed. This dill file must also +// contain the native assets mapping. +// When doing a hot reload, this mapping must stay in place. + +import 'dart:convert'; +import 'dart:io'; + +import 'package:file/file.dart'; +import 'package:yaml_edit/yaml_edit.dart' show YamlEditor; + +import '../../src/common.dart'; +import '../test_utils.dart' show ProcessResultMatcher, fileSystem, flutterBin, platform; +import '../transition_test_utils.dart'; +import 'native_assets_test_utils.dart'; + +final String hostOs = platform.operatingSystem; +const String packageName = 'data_asset_example'; + +void main() { + if (!platform.isMacOS && !platform.isLinux && !platform.isWindows) { + return; + } + + // Create project structure once as we can re-use this for executing the + // various test modes. + late final Directory tempDirectory; + late final Directory root; + setUpAll(() async { + processManager.runSync([flutterBin, 'config', '--enable-native-assets']); + processManager.runSync([flutterBin, 'config', '--enable-dart-data-assets']); + tempDirectory = fileSystem.directory( + fileSystem.systemTempDirectory.createTempSync().resolveSymbolicLinksSync(), + ); + root = await createDataAssetApp(packageName, tempDirectory); + }); + tearDownAll(() { + tryToDelete(tempDirectory); + }); + + group('dart data assets', () { + // NOTE: flutter-tester doesn't support profile/release mode. + // NOTE: flutter web doesn't allow cpaturing print()s in profile/release + // nOTE: flutter web doens't allow adding assets on hot-restart + final List devices = [hostOs, 'chrome', 'flutter-tester']; + final List modes = ['debug', 'release']; + + for (final String mode in modes) { + for (final String device in devices) { + final bool isFlutterTester = device == 'flutter-tester'; + final bool isWeb = device == 'chrome'; + final bool isDebug = mode == 'debug'; + + // This test relies on running the flutter app and capturing `print()`s + // the app prints to determine if the test succeeded. + // `flutter run --profile/release` on the web doesn't support capturing + // prints + // -> See https://github.com/flutter/flutter/issues/159668 + if (isWeb && !isDebug) { + continue; + } + + // Flutter tester only supports debug mode. + if (isFlutterTester && !isDebug) { + continue; + } + + testWithoutContext('flutter run on $device --$mode', () async { + final bool performRestart = isDebug; + final bool performReload = isDebug; + + final Map assets = {'id1': 'content1', 'id2': 'content2'}; + writeHookLibrary(root, assets, available: ['id1']); + writeHelperLibrary(root, 'version1', assets.keys.toList()); + + final ProcessTestResult result = await runFlutter( + ['run', '-v', '-d', device, '--$mode'], + root.path, + [ + Barrier.contains('Launching lib/main.dart on'), + Multiple.contains( + [ + // The flutter tool will print it's ready to accept keys (e.g. + // q=quit, ...) + // (This can be racy with app already running and printing) + 'Flutter run key command', + + // Once the app runs it will print whether it found assets. + 'VERSION: version1', + 'FOUND "packages/data_asset_example/id1": "content1".', + 'NOT-FOUND "packages/data_asset_example/id2".', + ], + handler: (_) { + if (!performRestart) { + return 'q'; + } + // Now we trigger a hot-restart with new assets & new + // application code, we make the build hook now emit also the + // `id2` data asset. + writeHookLibrary(root, assets, available: ['id1', 'id2']); + writeHelperLibrary(root, 'version2', assets.keys.toList()); + return 'R'; + }, + ), + if (performRestart) + Multiple.contains( + [ + // Once the app runs it will print whether it found assets. + // We expect it to having found the new `id2` now. + 'VERSION: version2', + 'FOUND "packages/data_asset_example/id1": "content1".', + + // Flutter web doesn't support new assets on hot-restart atm + // -> See https://github.com/flutter/flutter/issues/137265 + if (isWeb) + 'NOT-FOUND "packages/data_asset_example/id2".' + else + 'FOUND "packages/data_asset_example/id2": "content2".', + ], + handler: (_) { + if (!performReload) { + return 'q'; + } + // Now we trigger a hot-reload with new assets & new + // application code, we make the build hook now emit also the + // `id3` data asset (but not `id4`). + assets['id3'] = 'content3'; + assets['id4'] = 'content4'; + writeHookLibrary(root, assets, available: ['id1', 'id2', 'id3']); + writeHelperLibrary(root, 'version3', assets.keys.toList()); + return 'r'; + }, + ), + if (performReload) + Multiple.contains( + [ + // Once the app runs it will print whether it found assets. + 'VERSION: version3', + 'FOUND "packages/data_asset_example/id1": "content1".', + // Flutter web doesn't support new assets on hot-reload atm + // -> See https://github.com/flutter/flutter/issues/137265 + if (isWeb) ...[ + 'NOT-FOUND "packages/data_asset_example/id2".', + 'NOT-FOUND "packages/data_asset_example/id3".', + ] else ...[ + 'FOUND "packages/data_asset_example/id2": "content2".', + 'FOUND "packages/data_asset_example/id3": "content3".', + ], + 'NOT-FOUND "packages/data_asset_example/id4".', + ], + handler: (_) { + return 'q'; // quit + }, + ), + Barrier.contains('Application finished.'), + ], + debug: true, + ); + if (result.exitCode != 0) { + throw Exception( + 'flutter run failed: ${result.exitCode}\n${result.stderr}\n${result.stdout}', + ); + } + }); + } + } + + for (final String target in [hostOs, 'web']) { + testWithoutContext('flutter build $target', () async { + final Map assets = {'id1': 'content1', 'id2': 'content2'}; + final List available = ['id1']; + writeHookLibrary(root, assets, available: available); + writeHelperLibrary(root, 'version1', assets.keys.toList()); + + final ProcessTestResult result = await runFlutter( + ['build', '-v', target], + root.path, + [Barrier.contains('Built build/$target')], + debug: true, + ); + if (result.exitCode != 0) { + throw Exception( + 'flutter build failed: ${result.exitCode}\n${result.stderr}\n${result.stdout}', + ); + } + final Directory buildTargetDir = root.childDirectory('build').childDirectory(target); + + final List manifestFiles = + buildTargetDir + .listSync(recursive: true) + .whereType() + .where((File file) => file.path.endsWith('AssetManifest.json')) + .toList(); + + if (manifestFiles.isEmpty) { + throw Exception('Expected a `AssetManifest.json` to be avilable in the $buildTargetDir.'); + } + for (final File manifestFile in manifestFiles) { + final Map manifest = + json.decode(manifestFile.readAsStringSync()) as Map; + for (final String id in available) { + final String key = 'packages/$packageName/$id'; + final List entry = manifest[key]! as List; + expect(entry, equals([key])); + + final File file = manifestFile.parent.childFile(key); + expect(file.readAsStringSync(), assets[id]); + } + } + }); + } + }); +} + +Future createDataAssetApp(String packageName, Directory tempDirectory) async { + final ProcessResult result = processManager.runSync([ + flutterBin, + 'create', + '--no-pub', + packageName, + ], workingDirectory: tempDirectory.path); + expect(result, const ProcessResultMatcher()); + + final Directory root = tempDirectory.childDirectory(packageName); + + final File pubspecFile = root.childFile('pubspec.yaml'); + final String content = await pubspecFile.readAsString(); + final YamlEditor yamlEditor = YamlEditor(content); + yamlEditor.update(['dependencies'], {'native_assets_cli': '^0.11.0'}); + pubspecFile.writeAsStringSync(yamlEditor.toString()); + + await pinDependencies(pubspecFile); + + final File mainFile = root.childDirectory('lib').childFile('main.dart'); + writeFile(mainFile, ''' + import 'dart:async'; + + import 'package:flutter/material.dart'; + + import 'helper.dart'; + + void main() { + runApp(const MyApp()); + } + + class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + bool first = true; + Timer.periodic(const Duration(seconds: 1), (_) async { + // Delay to give the `flutter run` command time to connect and + // setup `print()` capturing logic (especially on web it won't be + // able to intercept prints until it has connected to DevTools). + if (first) { + await Future.delayed(const Duration(seconds: 5)); + } + dumpAssets(); + }); + + return MaterialApp( + title: 'Flutter Demo', + home: Scaffold( + body: Text('Hello world'), + ), + ); + } + } + '''); + + expect( + await processManager.run([flutterBin, 'pub', 'get'], workingDirectory: root.path), + const ProcessResultMatcher(), + ); + + return root; +} + +void writeHookLibrary( + Directory root, + Map dataAssets, { + required List available, +}) { + final Directory assetDir = root.childDirectory('asset'); + + dataAssets.forEach((String id, String content) { + writeFile(assetDir.childFile('$id.txt'), content); + }); + + final File hookFile = root.childDirectory('hook').childFile('build.dart'); + available = [for (final String id in available) '"$id"']; + writeFile(hookFile, ''' + import 'package:native_assets_cli/data_assets.dart'; + + void main(List args) async { + await build(args, (BuildInput input, BuildOutputBuilder output) async { + for (final id in $available) { + output.assets.data.add( + DataAsset( + package: input.packageName, + name: id, + file: input.packageRoot.resolve('asset/\$id.txt'), + ), + ); + } + + // This is a workaround for an issue in the + // `package:native_assets_builder` package: + // -> See https://github.com/dart-lang/native/issues/1770 + output.addDependency(input.packageRoot.resolve('hook/build.dart')); + }); + } + '''); +} + +void writeHelperLibrary(Directory root, String version, List assetIds) { + assetIds = [for (final String id in assetIds) '"packages/$packageName/$id"']; + final File helperFile = root.childDirectory('lib').childFile('helper.dart'); + writeFile(helperFile, ''' + import 'package:flutter/services.dart' show rootBundle; + + // Only run the code once, but after hot-restart & hot-reload we want to + // run it again. + bool $version = false; + void dumpAssets() async { + if ($version) return; + $version = true; + + final found = {}; + final notFound = []; + for (final String assetId in $assetIds) { + try { + found[assetId] = await rootBundle.loadString(assetId); + } catch (e, s) { + print('EXCEPTION \$e'); + notFound.add(assetId); + } + } + print('VERSION: $version'); + for (final MapEntry(:key, :value) in found.entries) { + print('FOUND "\$key": "\$value".'); + } + for (final id in notFound) { + print('NOT-FOUND "\$id".'); + } + } + '''); +} + +void writeFile(File file, String content) => + file + ..createSync(recursive: true) + ..writeAsStringSync(content); diff --git a/packages/flutter_tools/test/integration.shard/transition_test_utils.dart b/packages/flutter_tools/test/integration.shard/transition_test_utils.dart index 73f219ac83961..39210532a4958 100644 --- a/packages/flutter_tools/test/integration.shard/transition_test_utils.dart +++ b/packages/flutter_tools/test/integration.shard/transition_test_utils.dart @@ -26,7 +26,7 @@ void debugPrint(String message) { typedef LineHandler = String? Function(String line); abstract class Transition { - const Transition({this.handler, this.logging}); + const Transition({this.handler, this.logging, required this.contains}); /// Callback that is invoked when the transition matches. /// @@ -40,20 +40,24 @@ abstract class Transition { /// The default value, null, leaves the logging state unaffected. final bool? logging; + /// Whether to check the line for containing a substring or an exact match. + final bool contains; + bool matches(String line); @protected - bool lineMatchesPattern(String line, Pattern pattern, bool contains) { - if (pattern is RegExp) { - // Ideally this would also distinguish between "contains" and "equals" - // operation. - return line.contains(pattern); + bool lineMatchesPattern(String line, Pattern pattern) { + if (pattern is String) { + return contains ? line.contains(pattern) : line == pattern; } return contains ? line.contains(pattern) : line == pattern; } @protected - String describe(Pattern pattern, bool contains) { + String describe(Pattern pattern) { + if (pattern is String) { + return contains ? '"...$pattern..."' : '"$pattern"'; + } if (pattern is RegExp) { return '/${pattern.pattern}/'; } @@ -62,37 +66,35 @@ abstract class Transition { } class Barrier extends Transition { - Barrier(this.pattern, {super.handler, super.logging}) : contains = false; - Barrier.contains(this.pattern, {super.handler, super.logging}) : contains = true; + Barrier(this.pattern, {super.handler, super.logging}) : super(contains: false); + Barrier.contains(this.pattern, {super.handler, super.logging}) : super(contains: true); final Pattern pattern; - final bool contains; @override - bool matches(String line) => lineMatchesPattern(line, pattern, contains); + bool matches(String line) => lineMatchesPattern(line, pattern); @override - String toString() => describe(pattern, contains); + String toString() => describe(pattern); } class Multiple extends Transition { Multiple(List patterns, {super.handler, super.logging}) : _originalPatterns = patterns, patterns = patterns.toList(), - contains = false; + super(contains: false); Multiple.contains(List patterns, {super.handler, super.logging}) : _originalPatterns = patterns, patterns = patterns.toList(), - contains = true; + super(contains: true); final List _originalPatterns; final List patterns; - final bool contains; @override bool matches(String line) { for (int index = 0; index < patterns.length; index += 1) { - if (lineMatchesPattern(line, patterns[index], contains)) { + if (lineMatchesPattern(line, patterns[index])) { patterns.removeAt(index); break; } @@ -102,7 +104,7 @@ class Multiple extends Transition { @override String toString() { - String describe(Pattern pattern) => super.describe(pattern, contains); + String describe(Pattern pattern) => super.describe(pattern); if (patterns.isEmpty) { return '${_originalPatterns.map(describe).join(', ')} (all matched)'; } diff --git a/packages/flutter_tools/test/src/fakes.dart b/packages/flutter_tools/test/src/fakes.dart index 2d6499e71b9be..64ad74dd2a654 100644 --- a/packages/flutter_tools/test/src/fakes.dart +++ b/packages/flutter_tools/test/src/fakes.dart @@ -492,6 +492,7 @@ class TestFeatureFlags implements FeatureFlags { this.areCustomDevicesEnabled = false, this.isCliAnimationEnabled = true, this.isNativeAssetsEnabled = false, + this.isDartDataAssetsEnabled = false, this.isSwiftPackageManagerEnabled = false, this.isExplicitPackageDependenciesEnabled = false, }); @@ -526,6 +527,9 @@ class TestFeatureFlags implements FeatureFlags { @override final bool isNativeAssetsEnabled; + @override + final bool isDartDataAssetsEnabled; + @override final bool isSwiftPackageManagerEnabled; From 3f3ea7df168dd1ec7fbabbf44c158ce0bca26697 Mon Sep 17 00:00:00 2001 From: Moritz Date: Tue, 4 Mar 2025 09:52:00 +0100 Subject: [PATCH 02/25] Make target platform mandatory again --- .../src/build_system/targets/native_assets.dart | 14 ++++++++------ .../src/isolated/native_assets/native_assets.dart | 8 +++----- .../build_system/targets/native_assets_test.dart | 7 +++++++ 3 files changed, 18 insertions(+), 11 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 b26f499171a37..46df59b12bbc0 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 @@ -13,6 +13,7 @@ import '../../dart/package_map.dart'; import '../../isolated/native_assets/native_assets.dart'; import '../build_system.dart'; import '../depfile.dart'; +import '../exceptions.dart' show MissingDefineException; import 'common.dart'; export '../../isolated/native_assets/native_assets.dart' @@ -34,7 +35,7 @@ class DartBuild extends Target { final FileSystem fileSystem = environment.fileSystem; final DartBuildResult result; - final TargetPlatform? targetPlatform = + final TargetPlatform targetPlatform = specifiedTargetPlatform ?? _getTargetPlatformFromEnvironment(environment, name); final PackageConfig packageConfig = await loadPackageConfigWithLogging( @@ -142,7 +143,7 @@ class InstallCodeAssets extends Target { Future build(Environment environment) async { final Uri projectUri = environment.projectDir.uri; final FileSystem fileSystem = environment.fileSystem; - final TargetPlatform targetPlatform = _getTargetPlatformFromEnvironment(environment, name)!; + final TargetPlatform targetPlatform = _getTargetPlatformFromEnvironment(environment, name); // We fetch the result from the [DartBuild]. final DartBuildResult dartBuildResult = await DartBuild.loadBuildResult(environment); @@ -196,9 +197,10 @@ class InstallCodeAssets extends Target { static const String depFilename = 'install_code_assets.d'; } -TargetPlatform? _getTargetPlatformFromEnvironment(Environment environment, String name) { +TargetPlatform _getTargetPlatformFromEnvironment(Environment environment, String name) { final String? targetPlatformEnvironment = environment.defines[kTargetPlatform]; - return targetPlatformEnvironment != null - ? getTargetPlatformForName(targetPlatformEnvironment) - : null; + if (targetPlatformEnvironment == null) { + throw MissingDefineException(kTargetPlatform, name); + } + return getTargetPlatformForName(targetPlatformEnvironment); } 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 186a2f114d2d5..3898aaa05c2fd 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 @@ -126,8 +126,7 @@ Future runFlutterSpecificDartBuild({ required FileSystem fileSystem, }) async { final bool isWeb = targetPlatform == TargetPlatform.web_javascript; - final OS? targetOS = - targetPlatform != null ? getNativeOSFromTargetPlatform(targetPlatform) : null; + final OS? targetOS = isWeb ? null : getNativeOSFromTargetPlatform(targetPlatform!); assert(featureFlags.isNativeAssetsEnabled || targetOS != null); final Uri buildUri = nativeAssetsBuildUri(projectUri, targetOS); @@ -864,8 +863,7 @@ Never _throwNativeAssetsLinkFailed() { throwToolExit('Linking native assets failed. See the logs for more details.'); } -/// Returns null for the web, and non-null otherwise. -OS? getNativeOSFromTargetPlatform(TargetPlatform platform) { +OS getNativeOSFromTargetPlatform(TargetPlatform platform) { switch (platform) { case TargetPlatform.ios: return OS.iOS; @@ -897,7 +895,7 @@ OS? getNativeOSFromTargetPlatform(TargetPlatform platform) { throw StateError('Unknown operating system'); } case TargetPlatform.web_javascript: - return null; + throw StateError('No dart builds for web yet.'); } } diff --git a/packages/flutter_tools/test/general.shard/isolated/build_system/targets/native_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/build_system/targets/native_assets_test.dart index b9676354bf07d..9253ab25b2c52 100644 --- a/packages/flutter_tools/test/general.shard/isolated/build_system/targets/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/build_system/targets/native_assets_test.dart @@ -64,6 +64,13 @@ void main() { iosEnvironment.buildDir.createSync(recursive: true); androidEnvironment.buildDir.createSync(recursive: true); }); + testWithoutContext('NativeAssets throws error if missing target platform', () async { + iosEnvironment.defines.remove(kTargetPlatform); + expect( + const DartBuildForNative().build(iosEnvironment), + throwsA(isA()), + ); + }); testUsingContext('NativeAssets defaults to ios archs if missing', () async { writePackageConfigFile(directory: iosEnvironment.projectDir, mainLibName: 'my_app'); From 4faeadb357450b4ca45f1d72742675fb922523ac Mon Sep 17 00:00:00 2001 From: Moritz Date: Tue, 4 Mar 2025 11:31:07 +0100 Subject: [PATCH 03/25] Fix analysis errors --- .../src/isolated/native_assets/native_assets.dart | 13 ++++++------- .../isolated/native_assets/test/native_assets.dart | 6 +++--- 2 files changed, 9 insertions(+), 10 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 3898aaa05c2fd..aed2008366a27 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 @@ -128,7 +128,7 @@ Future runFlutterSpecificDartBuild({ final bool isWeb = targetPlatform == TargetPlatform.web_javascript; final OS? targetOS = isWeb ? null : getNativeOSFromTargetPlatform(targetPlatform!); assert(featureFlags.isNativeAssetsEnabled || targetOS != null); - final Uri buildUri = nativeAssetsBuildUri(projectUri, targetOS); + final Uri buildUri = nativeAssetsBuildUri(projectUri, isWeb ? 'web' : targetOS!.name); // Sanity check. final String? codesignIdentity = environmentDefines[kCodesignIdentity]; @@ -180,16 +180,15 @@ Future installCodeAssets({ required FileSystem fileSystem, required Uri nativeAssetsFileUri, }) async { - final OS? targetOS = getNativeOSFromTargetPlatform(targetPlatform); - assert(targetOS != null); + final OS targetOS = getNativeOSFromTargetPlatform(targetPlatform); - final Uri buildUri = nativeAssetsBuildUri(projectUri, targetOS); + final Uri buildUri = nativeAssetsBuildUri(projectUri, targetOS.name); final bool flutterTester = targetPlatform == TargetPlatform.tester; final BuildMode buildMode = _getBuildMode(environmentDefines, flutterTester); final String? codesignIdentity = environmentDefines[kCodesignIdentity]; final Map assetTargetLocations = assetTargetLocationsForOS( - targetOS!, + targetOS, dartBuildResult.codeAssets, flutterTester, buildUri, @@ -475,9 +474,9 @@ Future ensureNoNativeAssetsOrOsIsSupported( /// This should be the same for different archs, debug/release, etc. /// It should work for all macOS. -Uri nativeAssetsBuildUri(Uri projectUri, OS? os) { +Uri nativeAssetsBuildUri(Uri projectUri, String osName) { final String buildDir = getBuildDirectory(); - return projectUri.resolve('$buildDir/native_assets/${os == null ? 'web' : os.name}/'); + return projectUri.resolve('$buildDir/native_assets/$osName/'); } Map _assetTargetLocationsWindowsLinux( diff --git a/packages/flutter_tools/lib/src/isolated/native_assets/test/native_assets.dart b/packages/flutter_tools/lib/src/isolated/native_assets/test/native_assets.dart index ccefb5e596405..c0f255f9ca3a8 100644 --- a/packages/flutter_tools/lib/src/isolated/native_assets/test/native_assets.dart +++ b/packages/flutter_tools/lib/src/isolated/native_assets/test/native_assets.dart @@ -22,7 +22,7 @@ class TestCompilerNativeAssetsBuilderImpl implements TestCompilerNativeAssetsBui @override String windowsBuildDirectory(FlutterProject project) => - nativeAssetsBuildUri(project.directory.uri, OS.windows).toFilePath(); + nativeAssetsBuildUri(project.directory.uri, OS.windows.name).toFilePath(); } Future testCompilerBuildNativeAssets(BuildInfo buildInfo) async { @@ -53,8 +53,8 @@ Future testCompilerBuildNativeAssets(BuildInfo buildInfo) async { // Only `flutter test` uses the // `build/native_assets//native_assets.json` file which uses absolute // paths to the shared libraries. - final OS? targetOS = getNativeOSFromTargetPlatform(TargetPlatform.tester); - final Uri buildUri = nativeAssetsBuildUri(projectUri, targetOS); + final OS targetOS = getNativeOSFromTargetPlatform(TargetPlatform.tester); + final Uri buildUri = nativeAssetsBuildUri(projectUri, targetOS.name); final Uri nativeAssetsFileUri = buildUri.resolve('native_assets.json'); final Map environmentDefines = { From 6dbd09d8ea897ee438be3771092cae5d985b6ce4 Mon Sep 17 00:00:00 2001 From: Moritz Date: Tue, 4 Mar 2025 11:45:18 +0100 Subject: [PATCH 04/25] fix assert --- .../lib/src/isolated/native_assets/native_assets.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 aed2008366a27..9afff3bf9e236 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 @@ -127,7 +127,7 @@ Future runFlutterSpecificDartBuild({ }) async { final bool isWeb = targetPlatform == TargetPlatform.web_javascript; final OS? targetOS = isWeb ? null : getNativeOSFromTargetPlatform(targetPlatform!); - assert(featureFlags.isNativeAssetsEnabled || targetOS != null); + assert(!featureFlags.isNativeAssetsEnabled || targetOS != null); final Uri buildUri = nativeAssetsBuildUri(projectUri, isWeb ? 'web' : targetOS!.name); // Sanity check. From af3f9f13abe6e90c91c9c12d78c86bdc04a3bb8a Mon Sep 17 00:00:00 2001 From: Moritz Date: Tue, 4 Mar 2025 14:40:25 +0100 Subject: [PATCH 05/25] Renaming --- packages/flutter_tools/lib/src/asset.dart | 16 +++---- .../lib/src/build_system/targets/android.dart | 4 +- .../lib/src/build_system/targets/assets.dart | 8 ++-- .../lib/src/build_system/targets/common.dart | 4 +- .../lib/src/build_system/targets/ios.dart | 4 +- .../lib/src/build_system/targets/linux.dart | 4 +- .../lib/src/build_system/targets/macos.dart | 4 +- .../build_system/targets/native_assets.dart | 42 +++++++++---------- .../lib/src/build_system/targets/web.dart | 4 +- .../lib/src/build_system/targets/windows.dart | 4 +- .../isolated/native_assets/native_assets.dart | 40 +++++++++--------- .../native_assets/test/native_assets.dart | 4 +- .../lib/src/isolated/resident_web_runner.dart | 2 +- .../lib/src/resident_runner.dart | 14 +++---- packages/flutter_tools/lib/src/run_hot.dart | 2 +- .../test/general.shard/devfs_test.dart | 2 +- .../isolated/android/native_assets_test.dart | 8 ++-- .../targets/native_assets_test.dart | 6 +-- .../isolated/ios/native_assets_test.dart | 4 +- .../isolated/linux/native_assets_test.dart | 2 +- .../isolated/macos/native_assets_test.dart | 4 +- .../isolated/native_assets_test.dart | 14 +++---- .../isolated/windows/native_assets_test.dart | 4 +- 23 files changed, 99 insertions(+), 101 deletions(-) diff --git a/packages/flutter_tools/lib/src/asset.dart b/packages/flutter_tools/lib/src/asset.dart index 1f0ab433c0b64..e5fa16eecb19c 100644 --- a/packages/flutter_tools/lib/src/asset.dart +++ b/packages/flutter_tools/lib/src/asset.dart @@ -113,7 +113,7 @@ abstract class AssetBundle { /// Returns 0 for success; non-zero for failure. Future build({ - DartBuildResult? dartBuildResult, + DartHookResult? dartHookResult, String manifestPath = defaultManifestPath, required String packageConfigPath, bool deferredComponentsEnabled = false, @@ -189,7 +189,7 @@ class ManifestAssetBundle implements AssetBundle { DateTime? _lastBuildTimestamp; - DartBuildResult? _lastDartBuildResult; + DartHookResult? _lastBuildHookResult; // We assume the main asset is designed for a device pixel ratio of 1.0. static const String _kAssetManifestJsonFilename = 'AssetManifest.json'; @@ -211,12 +211,12 @@ class ManifestAssetBundle implements AssetBundle { @override bool needsBuild({String manifestPath = defaultManifestPath}) { if (!wasBuiltOnce() || - _lastDartBuildResult == null || + _lastBuildHookResult == null || // We need to re-run the dart build. - !_lastDartBuildResult!.isBuildUpToDate(_fileSystem) || + !_lastBuildHookResult!.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. - _lastDartBuildResult!.isBuildOutputDirty(_fileSystem)) { + _lastBuildHookResult!.isOutputDirty(_fileSystem)) { return true; } @@ -245,7 +245,7 @@ class ManifestAssetBundle implements AssetBundle { @override Future build({ - DartBuildResult? dartBuildResult, + DartHookResult? dartHookResult, String manifestPath = defaultManifestPath, FlutterProject? flutterProject, required String packageConfigPath, @@ -268,7 +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(); - _lastDartBuildResult = dartBuildResult; + _lastBuildHookResult = dartHookResult ?? DartHookResult.empty(); if (flutterManifest.isEmpty) { entries[_kAssetManifestJsonFilename] = AssetBundleEntry( DevFSStringContent('{}'), @@ -414,7 +414,7 @@ class ManifestAssetBundle implements AssetBundle { } } - for (final DataAsset dataAsset in dartBuildResult?.dataAssets ?? []) { + for (final DataAsset dataAsset in dartHookResult?.dataAssets ?? []) { final Package package = packageConfig[dataAsset.package]!; final Uri fileUri = dataAsset.file; 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 66aaec6c97ec9..d26bbf13de407 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/android.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/android.dart @@ -70,11 +70,11 @@ abstract class AndroidAssetBundle extends Target { .file(isolateSnapshotData) .copySync(outputDirectory.childFile('isolate_snapshot_data').path); } - final DartBuildResult dartBuildResult = await DartBuild.loadBuildResult(environment); + final DartHookResult dartHookResult = await DartBuild.loadBuildResult(environment); final Depfile assetDepfile = await copyAssets( environment, outputDirectory, - dartBuildResult: dartBuildResult, + dartHookResult: dartHookResult, targetPlatform: TargetPlatform.android, buildMode: buildMode, flavor: environment.defines[kFlavor], 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 b3424c9024a5b..ec9bc128da8ac 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/assets.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/assets.dart @@ -33,7 +33,7 @@ import 'native_assets.dart'; Future copyAssets( Environment environment, Directory outputDirectory, { - required DartBuildResult dartBuildResult, + required DartHookResult dartHookResult, Map additionalContent = const {}, required TargetPlatform targetPlatform, required BuildMode buildMode, @@ -50,7 +50,7 @@ Future copyAssets( splitDeferredAssets: buildMode != BuildMode.debug && buildMode != BuildMode.jitRelease, ).createBundle(); final int resultCode = await assetBundle.build( - dartBuildResult: dartBuildResult, + dartHookResult: dartHookResult, manifestPath: pubspecFile.path, packageConfigPath: findPackageConfigFileOrDefault(environment.projectDir).path, deferredComponentsEnabled: environment.defines[kDeferredComponents] == 'true', @@ -273,11 +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 DartBuildResult dartBuildResult = await DartBuild.loadBuildResult(environment); + final DartHookResult dartHookResult = await DartBuild.loadBuildResult(environment); final Depfile depfile = await copyAssets( environment, output, - dartBuildResult: dartBuildResult, + 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 d5f3902d321fd..cc05014b78628 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/common.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/common.dart @@ -84,11 +84,11 @@ class CopyFlutterBundle extends Target { .file(isolateSnapshotData) .copySync(environment.outputDir.childFile('isolate_snapshot_data').path); } - final DartBuildResult dartBuildResult = await DartBuild.loadBuildResult(environment); + final DartHookResult dartHookResult = await DartBuild.loadBuildResult(environment); final Depfile assetDepfile = await copyAssets( environment, environment.outputDir, - dartBuildResult: dartBuildResult, + dartHookResult: dartHookResult, targetPlatform: TargetPlatform.android, buildMode: buildMode, flavor: flavor, 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 987408e6f4b0b..5908b9e08a189 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/ios.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/ios.dart @@ -533,11 +533,11 @@ abstract class IosAssetBundle extends Target { final String? flavor = await flutterProject.ios.parseFlavorFromConfiguration(environment); // Copy the assets. - final DartBuildResult dartBuildResult = await DartBuild.loadBuildResult(environment); + final DartHookResult dartHookResult = await DartBuild.loadBuildResult(environment); final Depfile assetDepfile = await copyAssets( environment, assetDirectory, - dartBuildResult: dartBuildResult, + 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 a8c5f81946072..0996c35ad1799 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/linux.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/linux.dart @@ -128,11 +128,11 @@ abstract class BundleLinuxAssets extends Target { .copySync(outputDirectory.childFile('kernel_blob.bin').path); } final String versionInfo = getVersionInfo(environment.defines); - final DartBuildResult dartBuildResult = await DartBuild.loadBuildResult(environment); + final DartHookResult dartHookResult = await DartBuild.loadBuildResult(environment); final Depfile depfile = await copyAssets( environment, outputDirectory, - dartBuildResult: dartBuildResult, + 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 022f6f6283695..88f2358f16e34 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/macos.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/macos.dart @@ -494,11 +494,11 @@ abstract class MacOSBundleFlutterAssets extends Target { final FlutterProject flutterProject = FlutterProject.fromDirectory(environment.projectDir); final String? flavor = await flutterProject.macos.parseFlavorFromConfiguration(environment); - final DartBuildResult dartBuildResult = await DartBuild.loadBuildResult(environment); + final DartHookResult dartHookResult = await DartBuild.loadBuildResult(environment); final Depfile assetDepfile = await copyAssets( environment, assetDirectory, - dartBuildResult: dartBuildResult, + dartHookResult: dartHookResult, targetPlatform: TargetPlatform.darwin, buildMode: buildMode, flavor: flavor, 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 46df59b12bbc0..2887f4b72e381 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 @@ -17,7 +17,7 @@ import '../exceptions.dart' show MissingDefineException; import 'common.dart'; export '../../isolated/native_assets/native_assets.dart' - show CodeAsset, DartBuildResult, DataAsset, DynamicLoadingBundled; + show CodeAsset, DartHookResult, DataAsset, DynamicLoadingBundled; /// Runs the dart build of the app. class DartBuild extends Target { @@ -33,7 +33,7 @@ class DartBuild extends Target { @override Future build(Environment environment) async { final FileSystem fileSystem = environment.fileSystem; - final DartBuildResult result; + final DartHookResult result; final TargetPlatform targetPlatform = specifiedTargetPlatform ?? _getTargetPlatformFromEnvironment(environment, name); @@ -54,7 +54,7 @@ class DartBuild extends Target { environment.logger, runPackageName!, ); - result = await runFlutterSpecificDartBuild( + result = await runFlutterSpecificHooks( environmentDefines: environment.defines, buildRunner: buildRunner, targetPlatform: targetPlatform, @@ -62,15 +62,15 @@ class DartBuild extends Target { fileSystem: fileSystem, ); - final File dartBuildResultJsonFile = environment.buildDir.childFile(dartBuildResultFilename); - if (!dartBuildResultJsonFile.parent.existsSync()) { - dartBuildResultJsonFile.parent.createSync(recursive: true); + 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()) { @@ -99,28 +99,26 @@ class DartBuild extends Target { 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 loadBuildResult(Environment environment) async { + final File dartHookResultJsonFile = environment.buildDir.childFile( + DartBuild.dartHookResultFilename, ); - if (!dartBuildResultJsonFile.existsSync()) { - return DartBuildResult.empty(); + if (!dartHookResultJsonFile.existsSync()) { + return DartHookResult.empty(); } - return DartBuildResult.fromJson( - json.decode(dartBuildResultJsonFile.readAsStringSync()) as Map, + return DartHookResult.fromJson( + json.decode(dartHookResultJsonFile.readAsStringSync()) as Map, ); } @override List get dependencies => []; - static const String dartBuildResultFilename = 'dart_build_result.json'; + static const String dartHookResultFilename = 'dart_build_result.json'; static const String depFilename = 'dart_build.d'; } @@ -146,13 +144,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.loadBuildResult(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, @@ -162,7 +160,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); 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 a1c1da51b54ad..70541295844c6 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/web.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/web.dart @@ -453,11 +453,11 @@ class WebReleaseBundle extends Target { final Directory outputDirectory = environment.outputDir.childDirectory('assets'); outputDirectory.createSync(recursive: true); - final DartBuildResult dartBuildResult = await DartBuild.loadBuildResult(environment); + final DartHookResult dartHookResult = await DartBuild.loadBuildResult(environment); final Depfile depfile = await copyAssets( environment, environment.outputDir.childDirectory('assets'), - dartBuildResult: dartBuildResult, + 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 a94e4f521cef1..b0d432eda30ac 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/windows.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/windows.dart @@ -142,11 +142,11 @@ abstract class BundleWindowsAssets extends Target { .childFile('app.dill') .copySync(outputDirectory.childFile('kernel_blob.bin').path); } - final DartBuildResult dartBuildResult = await DartBuild.loadBuildResult(environment); + final DartHookResult dartHookResult = await DartBuild.loadBuildResult(environment); final Depfile depfile = await copyAssets( environment, outputDirectory, - dartBuildResult: dartBuildResult, + dartHookResult: dartHookResult, targetPlatform: targetPlatform, buildMode: buildMode, additionalContent: { 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 9afff3bf9e236..20f0bfd797798 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 @@ -31,12 +31,12 @@ import 'windows/native_assets.dart'; 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 build and the dependencies of those assets. +/// The assets produced by a Dart hooks 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( +final class DartHookResult { + const DartHookResult( this.buildStart, this.buildEnd, this.codeAssets, @@ -44,14 +44,14 @@ final class DartBuildResult { this.dependencies, ); - DartBuildResult.empty() + DartHookResult.empty() : buildStart = DateTime.now(), buildEnd = DateTime.now(), codeAssets = const [], dataAssets = const [], dependencies = const []; - factory DartBuildResult.fromJson(Map json) { + 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 = [ @@ -66,7 +66,7 @@ final class DartBuildResult { for (final Object? json in json['data_assets'] as List? ?? const []) DataAsset.fromEncoded(EncodedAsset.fromJson(json! as Map)), ]; - return DartBuildResult(buildStart, buildEnd, codeAssets, dataAssets, dependencies); + return DartHookResult(buildStart, buildEnd, codeAssets, dataAssets, dependencies); } final DateTime buildStart; @@ -91,7 +91,7 @@ final class DartBuildResult { ]; /// Whether caller may need to re-run the dart build. - bool isBuildUpToDate(FileSystem fileSystem) { + bool isUpToDate(FileSystem fileSystem) { return !_wasAnyFileModifiedSince(fileSystem, buildStart, dependencies); } @@ -101,7 +101,7 @@ final class DartBuildResult { /// the output may be existing on disc and not be produced by the build /// itself - in which case we may not need to re-build if the file changes, /// but we may need to make a new asset bundle with the modified file). - bool isBuildOutputDirty(FileSystem fileSystem) { + bool isOutputDirty(FileSystem fileSystem) { return _wasAnyFileModifiedSince(fileSystem, buildEnd, filesToBeBundled); } @@ -116,9 +116,9 @@ final class DartBuildResult { } } -/// Invokes the build of all transitive Dart packages and prepares code assets +/// Invokes the build of all transitive Dart package hooks and prepares assets /// to be included in the native build. -Future runFlutterSpecificDartBuild({ +Future runFlutterSpecificHooks({ required Map environmentDefines, required FlutterNativeAssetsBuildRunner buildRunner, required TargetPlatform? targetPlatform, @@ -144,8 +144,8 @@ Future runFlutterSpecificDartBuild({ await buildDir.create(recursive: true); } - if (!await _nativeBuildRequired(buildRunner)) { - return DartBuildResult.empty(); + if (!await _hookRunRequired(buildRunner)) { + return DartHookResult.empty(); } final BuildMode buildMode = _getBuildMode(environmentDefines, flutterTester); @@ -155,10 +155,10 @@ Future runFlutterSpecificDartBuild({ : (flutterTester ? [Architecture.current] : _architecturesForOS(targetPlatform, targetOS!, environmentDefines)); - final DartBuildResult result = + final DartHookResult result = architectures?.isEmpty ?? false - ? DartBuildResult.empty() - : await _runDartBuild( + ? DartHookResult.empty() + : await _runDartHooks( environmentDefines: environmentDefines, buildRunner: buildRunner, codeAssetSupport: !isWeb && featureFlags.isNativeAssetsEnabled, @@ -173,7 +173,7 @@ Future runFlutterSpecificDartBuild({ } Future installCodeAssets({ - required DartBuildResult dartBuildResult, + required DartHookResult dartHookResult, required Map environmentDefines, required TargetPlatform targetPlatform, required Uri projectUri, @@ -189,7 +189,7 @@ Future installCodeAssets({ final String? codesignIdentity = environmentDefines[kCodesignIdentity]; final Map assetTargetLocations = assetTargetLocationsForOS( targetOS, - dartBuildResult.codeAssets, + dartHookResult.codeAssets, flutterTester, buildUri, ); @@ -427,7 +427,7 @@ bool _nativeAssetsLinkingEnabled(BuildMode buildMode) { } } -Future _nativeBuildRequired(FlutterNativeAssetsBuildRunner buildRunner) async { +Future _hookRunRequired(FlutterNativeAssetsBuildRunner buildRunner) async { final List packagesWithNativeAssets = await buildRunner.packagesWithNativeAssets(); if (packagesWithNativeAssets.isEmpty) { globals.logger.printTrace( @@ -618,7 +618,7 @@ Future _copyNativeCodeAssetsForOS( /// /// This will invoke `hook/build.dart` and `hook/link.dart` (if applicable) for /// all transitive dart packages that define such hooks. -Future _runDartBuild({ +Future _runDartHooks({ required Map environmentDefines, required FlutterNativeAssetsBuildRunner buildRunner, required List? architectures, @@ -774,7 +774,7 @@ Future _runDartBuild({ globals.logger.printTrace('Building native assets for $targetString done.'); final DateTime buildEnd = DateTime.now(); - return DartBuildResult(buildStart, buildEnd, codeAssets, dataAssets, dependencies.toList()); + return DartHookResult(buildStart, buildEnd, codeAssets, dataAssets, dependencies.toList()); } List _architecturesForOS( diff --git a/packages/flutter_tools/lib/src/isolated/native_assets/test/native_assets.dart b/packages/flutter_tools/lib/src/isolated/native_assets/test/native_assets.dart index c0f255f9ca3a8..46d5801c02f7c 100644 --- a/packages/flutter_tools/lib/src/isolated/native_assets/test/native_assets.dart +++ b/packages/flutter_tools/lib/src/isolated/native_assets/test/native_assets.dart @@ -62,7 +62,7 @@ Future testCompilerBuildNativeAssets(BuildInfo buildInfo) async { }; // First perform the dart build. - final DartBuildResult dartBuildResult = await runFlutterSpecificDartBuild( + final DartHookResult dartHookResult = await runFlutterSpecificHooks( environmentDefines: environmentDefines, buildRunner: buildRunner, targetPlatform: TargetPlatform.tester, @@ -72,7 +72,7 @@ Future testCompilerBuildNativeAssets(BuildInfo buildInfo) async { // Then "install" the code assets so they can be used at runtime. await installCodeAssets( - dartBuildResult: dartBuildResult, + dartHookResult: dartHookResult, environmentDefines: environmentDefines, targetPlatform: TargetPlatform.tester, projectUri: projectUri, diff --git a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart index 556600147bb24..73d22f5b7fe7d 100644 --- a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart +++ b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart @@ -643,7 +643,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). if (rebuildBundle) { _logger.printTrace('Updating assets'); final int result = await assetBundle.build( - dartBuildResult: await runDartBuild(targetPlatform: TargetPlatform.web_javascript), + dartHookResult: await runDartBuild(targetPlatform: TargetPlatform.web_javascript), packageConfigPath: debuggingOptions.buildInfo.packageConfigPath, targetPlatform: TargetPlatform.web_javascript, ); diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index cfeb8acfa5fd1..ce9cf191bce17 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -1075,10 +1075,10 @@ abstract class ResidentRunner extends ResidentHandlers { /// The result of the last dart build. Will be populated in /// [runDartBuild]. - DartBuildResult? _dartBuildResult; + DartHookResult? _dartHookResult; /// The last dart build's result. - DartBuildResult? get dartBuildResult => _dartBuildResult; + DartHookResult? get dartHookResult => _dartHookResult; @override bool hotMode; @@ -1197,11 +1197,11 @@ abstract class ResidentRunner extends ResidentHandlers { globals.printTrace('complete'); } - Future runDartBuild({required TargetPlatform targetPlatform}) async { + Future runDartBuild({required TargetPlatform targetPlatform}) async { globals.printTrace('runDartBuild() with ${_environment.defines} and $targetPlatform'); - if (_dartBuildResult?.isBuildUpToDate(globals.fs) ?? false) { + if (_dartHookResult?.isUpToDate(globals.fs) ?? false) { globals.printTrace('runDartBuild() - up-to-date already'); - return _dartBuildResult!; + return _dartHookResult!; } globals.printTrace('runDartBuild() - will perform dart build'); @@ -1218,9 +1218,9 @@ abstract class ResidentRunner extends ResidentHandlers { } } - _dartBuildResult = await DartBuild.loadBuildResult(_environment); + _dartHookResult = await DartBuild.loadBuildResult(_environment); globals.printTrace('runDartBuild() - done'); - return _dartBuildResult!; + return _dartHookResult!; } @protected diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart index f347dca11bf97..db871a21b469a 100644 --- a/packages/flutter_tools/lib/src/run_hot.dart +++ b/packages/flutter_tools/lib/src/run_hot.dart @@ -507,7 +507,7 @@ class HotRunner extends ResidentRunner { if (rebuildBundle) { globals.printTrace('Updating assets'); final int result = await assetBundle.build( - dartBuildResult: await runDartBuild(targetPlatform: targetPlatform), + dartHookResult: await runDartBuild(targetPlatform: targetPlatform), packageConfigPath: debuggingOptions.buildInfo.packageConfigPath, flavor: debuggingOptions.buildInfo.flavor, ); diff --git a/packages/flutter_tools/test/general.shard/devfs_test.dart b/packages/flutter_tools/test/general.shard/devfs_test.dart index 2bfbe680c4199..ce085e3666be5 100644 --- a/packages/flutter_tools/test/general.shard/devfs_test.dart +++ b/packages/flutter_tools/test/general.shard/devfs_test.dart @@ -991,7 +991,7 @@ class FakeBundle extends AssetBundle { @override Future build({ - DartBuildResult? dartBuildResult, + DartHookResult? dartHookResult, String manifestPath = defaultManifestPath, String? assetDirPath, String? packageConfigPath, 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 3d188e9233867..b69056cc16e61 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 @@ -89,7 +89,7 @@ void main() { kBuildMode: buildMode.cliName, kMinSdkVersion: minSdkVersion, }; - final DartBuildResult result = await runFlutterSpecificDartBuild( + final DartHookResult result = await runFlutterSpecificHooks( environmentDefines: environmentDefines, targetPlatform: TargetPlatform.android_arm64, projectUri: projectUri, @@ -97,7 +97,7 @@ void main() { buildRunner: buildRunner, ); await installCodeAssets( - dartBuildResult: result, + dartHookResult: result, environmentDefines: environmentDefines, targetPlatform: TargetPlatform.android_arm64, projectUri: projectUri, @@ -130,7 +130,7 @@ void main() { () async { final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); await packageConfig.create(recursive: true); - await runFlutterSpecificDartBuild( + await runFlutterSpecificHooks( environmentDefines: { kBuildMode: BuildMode.debug.cliName, kMinSdkVersion: minSdkVersion, @@ -155,7 +155,7 @@ void main() { await packageConfig.parent.create(); await packageConfig.create(); expect( - () => runFlutterSpecificDartBuild( + () => runFlutterSpecificHooks( environmentDefines: { kBuildMode: BuildMode.debug.cliName, kMinSdkVersion: minSdkVersion, diff --git a/packages/flutter_tools/test/general.shard/isolated/build_system/targets/native_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/build_system/targets/native_assets_test.dart index 9253ab25b2c52..d7790a7aaee21 100644 --- a/packages/flutter_tools/test/general.shard/isolated/build_system/targets/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/build_system/targets/native_assets_test.dart @@ -213,14 +213,14 @@ void main() { // * dart build output should depend on C source // * installation output should depend on shared library from dart build - final File dartBuildResult = iosEnvironment.buildDir.childFile( - DartBuild.dartBuildResultFilename, + final File dartHookResult = iosEnvironment.buildDir.childFile( + DartBuild.dartHookResultFilename, ); final File buildDepsFile = iosEnvironment.buildDir.childFile(DartBuild.depFilename); expect(buildDepsFile, exists); expect( buildDepsFile.readAsStringSync(), - stringContainsInOrder([dartBuildResult.path, ':', 'src/foo.c']), + stringContainsInOrder([dartHookResult.path, ':', 'src/foo.c']), ); final File nativeAssetsYaml = iosEnvironment.buildDir.childFile( diff --git a/packages/flutter_tools/test/general.shard/isolated/ios/native_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/ios/native_assets_test.dart index 5d0c43c992f7d..4750a9fe23b7f 100644 --- a/packages/flutter_tools/test/general.shard/isolated/ios/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/ios/native_assets_test.dart @@ -199,7 +199,7 @@ void main() { kSdkRoot: '.../iPhone Simulator', kIosArchs: 'arm64 x86_64', }; - final DartBuildResult dartBuildResult = await runFlutterSpecificDartBuild( + final DartHookResult dartHookResult = await runFlutterSpecificHooks( environmentDefines: environmentDefines, targetPlatform: TargetPlatform.ios, projectUri: projectUri, @@ -207,7 +207,7 @@ void main() { buildRunner: buildRunner, ); await installCodeAssets( - dartBuildResult: dartBuildResult, + dartHookResult: dartHookResult, environmentDefines: environmentDefines, targetPlatform: TargetPlatform.ios, projectUri: projectUri, diff --git a/packages/flutter_tools/test/general.shard/isolated/linux/native_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/linux/native_assets_test.dart index f1a2796c9945d..5508644f30b5c 100644 --- a/packages/flutter_tools/test/general.shard/isolated/linux/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/linux/native_assets_test.dart @@ -61,7 +61,7 @@ void main() { final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); await packageConfig.create(recursive: true); - await runFlutterSpecificDartBuild( + await runFlutterSpecificHooks( environmentDefines: {kBuildMode: BuildMode.debug.cliName}, targetPlatform: TargetPlatform.linux_x64, projectUri: projectUri, diff --git a/packages/flutter_tools/test/general.shard/isolated/macos/native_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/macos/native_assets_test.dart index e03f65fba30e4..8984ebbc62007 100644 --- a/packages/flutter_tools/test/general.shard/isolated/macos/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/macos/native_assets_test.dart @@ -300,7 +300,7 @@ void main() { }; final TargetPlatform targetPlatform = flutterTester ? TargetPlatform.tester : TargetPlatform.darwin; - final DartBuildResult dartBuildResult = await runFlutterSpecificDartBuild( + final DartHookResult dartHookResult = await runFlutterSpecificHooks( environmentDefines: environmentDefines, targetPlatform: targetPlatform, projectUri: projectUri, @@ -315,7 +315,7 @@ void main() { : nonFlutterTesterAssetUri; await installCodeAssets( - dartBuildResult: dartBuildResult, + dartHookResult: dartHookResult, environmentDefines: environmentDefines, targetPlatform: targetPlatform, projectUri: projectUri, 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 b2f61ed57cbde..7f55f507966eb 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 @@ -77,7 +77,7 @@ void main() { makeCodeAsset('free', LookupInExecutable()), makeCodeAsset('draw', DynamicLoadingSystem(Uri.file('/usr/lib/skia.so'))), ]; - final DartBuildResult dartBuildResult = await runFlutterSpecificDartBuild( + final DartHookResult dartHookResult = await runFlutterSpecificHooks( environmentDefines: environmentDefines, targetPlatform: TargetPlatform.linux_x64, projectUri: projectUri, @@ -89,7 +89,7 @@ void main() { ), ); await installCodeAssets( - dartBuildResult: dartBuildResult, + dartHookResult: dartHookResult, environmentDefines: environmentDefines, targetPlatform: TargetPlatform.windows_x64, projectUri: projectUri, @@ -108,7 +108,7 @@ void main() { await packageConfig.parent.create(); await packageConfig.create(); expect( - () => runFlutterSpecificDartBuild( + () => runFlutterSpecificHooks( environmentDefines: {kBuildMode: BuildMode.debug.cliName}, targetPlatform: TargetPlatform.windows_x64, projectUri: projectUri, @@ -138,7 +138,7 @@ void main() { final Map environmentDefines = { kBuildMode: BuildMode.debug.cliName, }; - final DartBuildResult dartBuildResult = await runFlutterSpecificDartBuild( + final DartHookResult dartHookResult = await runFlutterSpecificHooks( environmentDefines: environmentDefines, targetPlatform: TargetPlatform.windows_x64, projectUri: projectUri, @@ -148,7 +148,7 @@ void main() { ), ); await installCodeAssets( - dartBuildResult: dartBuildResult, + dartHookResult: dartHookResult, environmentDefines: environmentDefines, targetPlatform: TargetPlatform.windows_x64, projectUri: projectUri, @@ -180,7 +180,7 @@ void main() { await packageConfig.parent.create(); await packageConfig.create(); expect( - () => runFlutterSpecificDartBuild( + () => runFlutterSpecificHooks( environmentDefines: {kBuildMode: BuildMode.debug.cliName}, targetPlatform: TargetPlatform.linux_x64, projectUri: projectUri, @@ -222,7 +222,7 @@ void main() { file: file, ); - final DartBuildResult result = await runFlutterSpecificDartBuild( + final DartHookResult result = await runFlutterSpecificHooks( environmentDefines: { // Release mode means the dart build has linking enabled. kBuildMode: BuildMode.release.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 36ab9f068f046..a5ee8b7c7dc02 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 @@ -107,7 +107,7 @@ void main() { }; final TargetPlatform targetPlatform = flutterTester ? TargetPlatform.tester : TargetPlatform.windows_x64; - final DartBuildResult dartBuildResult = await runFlutterSpecificDartBuild( + final DartHookResult dartHookResult = await runFlutterSpecificHooks( environmentDefines: environmentDefines, targetPlatform: targetPlatform, projectUri: projectUri, @@ -123,7 +123,7 @@ void main() { ) : nonFlutterTesterAssetUri; await installCodeAssets( - dartBuildResult: dartBuildResult, + dartHookResult: dartHookResult, environmentDefines: environmentDefines, targetPlatform: targetPlatform, projectUri: projectUri, From e0cf6fd647fdba71d1abb5910e35d1d6d985218f Mon Sep 17 00:00:00 2001 From: Moritz Date: Tue, 4 Mar 2025 14:51:09 +0100 Subject: [PATCH 06/25] Some more slight renaming --- packages/flutter_tools/lib/src/asset.dart | 10 +++++----- .../lib/src/build_system/targets/android.dart | 2 +- .../lib/src/build_system/targets/assets.dart | 2 +- .../lib/src/build_system/targets/common.dart | 2 +- .../lib/src/build_system/targets/ios.dart | 2 +- .../lib/src/build_system/targets/linux.dart | 2 +- .../lib/src/build_system/targets/macos.dart | 2 +- .../lib/src/build_system/targets/native_assets.dart | 4 ++-- .../lib/src/build_system/targets/web.dart | 2 +- .../lib/src/build_system/targets/windows.dart | 2 +- .../lib/src/isolated/native_assets/native_assets.dart | 2 +- packages/flutter_tools/lib/src/resident_runner.dart | 2 +- 12 files changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/flutter_tools/lib/src/asset.dart b/packages/flutter_tools/lib/src/asset.dart index e5fa16eecb19c..162e01e2d2bb2 100644 --- a/packages/flutter_tools/lib/src/asset.dart +++ b/packages/flutter_tools/lib/src/asset.dart @@ -189,7 +189,7 @@ class ManifestAssetBundle implements AssetBundle { DateTime? _lastBuildTimestamp; - DartHookResult? _lastBuildHookResult; + DartHookResult? _lastHookResult; // We assume the main asset is designed for a device pixel ratio of 1.0. static const String _kAssetManifestJsonFilename = 'AssetManifest.json'; @@ -211,12 +211,12 @@ class ManifestAssetBundle implements AssetBundle { @override bool needsBuild({String manifestPath = defaultManifestPath}) { if (!wasBuiltOnce() || - _lastBuildHookResult == null || + _lastHookResult == null || // We need to re-run the dart build. - !_lastBuildHookResult!.isUpToDate(_fileSystem) || + !_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. - _lastBuildHookResult!.isOutputDirty(_fileSystem)) { + _lastHookResult!.isOutputDirty(_fileSystem)) { return true; } @@ -268,7 +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(); - _lastBuildHookResult = dartHookResult ?? DartHookResult.empty(); + _lastHookResult = dartHookResult ?? DartHookResult.empty(); if (flutterManifest.isEmpty) { entries[_kAssetManifestJsonFilename] = AssetBundleEntry( DevFSStringContent('{}'), 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 d26bbf13de407..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,7 +70,7 @@ abstract class AndroidAssetBundle extends Target { .file(isolateSnapshotData) .copySync(outputDirectory.childFile('isolate_snapshot_data').path); } - final DartHookResult dartHookResult = await DartBuild.loadBuildResult(environment); + final DartHookResult dartHookResult = await DartBuild.loadHookResult(environment); final Depfile assetDepfile = await copyAssets( environment, outputDirectory, 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 ec9bc128da8ac..9f6eacff9515c 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/assets.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/assets.dart @@ -273,7 +273,7 @@ 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.loadBuildResult(environment); + final DartHookResult dartHookResult = await DartBuild.loadHookResult(environment); final Depfile depfile = await copyAssets( environment, output, 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 cc05014b78628..6324fb6f12c0a 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/common.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/common.dart @@ -84,7 +84,7 @@ class CopyFlutterBundle extends Target { .file(isolateSnapshotData) .copySync(environment.outputDir.childFile('isolate_snapshot_data').path); } - final DartHookResult dartHookResult = await DartBuild.loadBuildResult(environment); + final DartHookResult dartHookResult = await DartBuild.loadHookResult(environment); final Depfile assetDepfile = await copyAssets( environment, environment.outputDir, 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 5908b9e08a189..fc983e0a94931 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/ios.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/ios.dart @@ -533,7 +533,7 @@ abstract class IosAssetBundle extends Target { final String? flavor = await flutterProject.ios.parseFlavorFromConfiguration(environment); // Copy the assets. - final DartHookResult dartHookResult = await DartBuild.loadBuildResult(environment); + final DartHookResult dartHookResult = await DartBuild.loadHookResult(environment); final Depfile assetDepfile = await copyAssets( environment, assetDirectory, 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 0996c35ad1799..0d03a40cb7e98 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/linux.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/linux.dart @@ -128,7 +128,7 @@ abstract class BundleLinuxAssets extends Target { .copySync(outputDirectory.childFile('kernel_blob.bin').path); } final String versionInfo = getVersionInfo(environment.defines); - final DartHookResult dartHookResult = await DartBuild.loadBuildResult(environment); + final DartHookResult dartHookResult = await DartBuild.loadHookResult(environment); final Depfile depfile = await copyAssets( environment, outputDirectory, 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 88f2358f16e34..74cbc24701576 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/macos.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/macos.dart @@ -494,7 +494,7 @@ 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.loadBuildResult(environment); + final DartHookResult dartHookResult = await DartBuild.loadHookResult(environment); final Depfile assetDepfile = await copyAssets( environment, assetDirectory, 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 2887f4b72e381..e187f903d4126 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 @@ -103,7 +103,7 @@ class DartBuild extends Target { /// Dependent build [Target]s can use this to consume the result of the /// [DartBuild] target. - static Future loadBuildResult(Environment environment) async { + static Future loadHookResult(Environment environment) async { final File dartHookResultJsonFile = environment.buildDir.childFile( DartBuild.dartHookResultFilename, ); @@ -144,7 +144,7 @@ class InstallCodeAssets extends Target { final TargetPlatform targetPlatform = _getTargetPlatformFromEnvironment(environment, name); // We fetch the result from the [DartBuild]. - final DartHookResult dartHookResult = 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. 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 70541295844c6..67bafd481cb28 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/web.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/web.dart @@ -453,7 +453,7 @@ class WebReleaseBundle extends Target { final Directory outputDirectory = environment.outputDir.childDirectory('assets'); outputDirectory.createSync(recursive: true); - final DartHookResult dartHookResult = await DartBuild.loadBuildResult(environment); + final DartHookResult dartHookResult = await DartBuild.loadHookResult(environment); final Depfile depfile = await copyAssets( environment, environment.outputDir.childDirectory('assets'), 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 b0d432eda30ac..bc0394e81b05f 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/windows.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/windows.dart @@ -142,7 +142,7 @@ abstract class BundleWindowsAssets extends Target { .childFile('app.dill') .copySync(outputDirectory.childFile('kernel_blob.bin').path); } - final DartHookResult dartHookResult = await DartBuild.loadBuildResult(environment); + final DartHookResult dartHookResult = await DartBuild.loadHookResult(environment); final Depfile depfile = await copyAssets( environment, outputDirectory, 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 20f0bfd797798..788db257f9bbc 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 @@ -31,7 +31,7 @@ import 'windows/native_assets.dart'; 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 hooks and the dependencies of those assets. +/// 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. diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index ce9cf191bce17..406fb62ce89a1 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -1218,7 +1218,7 @@ abstract class ResidentRunner extends ResidentHandlers { } } - _dartHookResult = await DartBuild.loadBuildResult(_environment); + _dartHookResult = await DartBuild.loadHookResult(_environment); globals.printTrace('runDartBuild() - done'); return _dartHookResult!; } From 6b67180c0e3f72dd284323153472f662ef336a37 Mon Sep 17 00:00:00 2001 From: Moritz Date: Tue, 4 Mar 2025 15:50:16 +0100 Subject: [PATCH 07/25] Fix test failures --- .../lib/src/isolated/native_assets/native_assets.dart | 2 +- packages/flutter_tools/lib/src/run_hot.dart | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) 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 788db257f9bbc..c41ee232bf489 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 @@ -635,7 +635,7 @@ Future _runDartHooks({ // For web builds we use `null` as the single architecture. final bool isWeb = architectures == null; - final String targetString = isWeb ? 'web' : '$targetOS ${architectures.join(',')}'; + final String targetString = isWeb ? 'web' : '$targetOS $architectures'; globals.logger.printTrace('Building native assets for $targetString.'); final List assets = []; diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart index db871a21b469a..633c76efcb8b6 100644 --- a/packages/flutter_tools/lib/src/run_hot.dart +++ b/packages/flutter_tools/lib/src/run_hot.dart @@ -309,6 +309,8 @@ class HotRunner extends ResidentRunner { return 3; } + await _calculateTargetPlatform(); + final Stopwatch initialUpdateDevFSsTimer = Stopwatch()..start(); final UpdateFSReport devfsResult = await _updateDevFS( fullRestart: needsFullRestart, From a0b5e04978b87e10b8377912d0eb0544c9369423 Mon Sep 17 00:00:00 2001 From: Moritz Date: Tue, 4 Mar 2025 16:21:44 +0100 Subject: [PATCH 08/25] Fix test format --- .../lib/src/isolated/native_assets/native_assets.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 c41ee232bf489..97b6bdc3f2f5a 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 @@ -635,7 +635,10 @@ Future _runDartHooks({ // For web builds we use `null` as the single architecture. final bool isWeb = architectures == null; - final String targetString = isWeb ? 'web' : '$targetOS $architectures'; + final String targetString = + isWeb + ? 'web' + : '$targetOS ${architectures.length == 1 ? architectures.single : architectures}'; globals.logger.printTrace('Building native assets for $targetString.'); final List assets = []; From 08b1bdecd6a632e7b30901efaaf19d0f759bd0f5 Mon Sep 17 00:00:00 2001 From: Moritz Date: Wed, 5 Mar 2025 11:23:55 +0100 Subject: [PATCH 09/25] Revert changes to transition test utils --- .../transition_test_utils.dart | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/packages/flutter_tools/test/integration.shard/transition_test_utils.dart b/packages/flutter_tools/test/integration.shard/transition_test_utils.dart index 39210532a4958..73f219ac83961 100644 --- a/packages/flutter_tools/test/integration.shard/transition_test_utils.dart +++ b/packages/flutter_tools/test/integration.shard/transition_test_utils.dart @@ -26,7 +26,7 @@ void debugPrint(String message) { typedef LineHandler = String? Function(String line); abstract class Transition { - const Transition({this.handler, this.logging, required this.contains}); + const Transition({this.handler, this.logging}); /// Callback that is invoked when the transition matches. /// @@ -40,24 +40,20 @@ abstract class Transition { /// The default value, null, leaves the logging state unaffected. final bool? logging; - /// Whether to check the line for containing a substring or an exact match. - final bool contains; - bool matches(String line); @protected - bool lineMatchesPattern(String line, Pattern pattern) { - if (pattern is String) { - return contains ? line.contains(pattern) : line == pattern; + bool lineMatchesPattern(String line, Pattern pattern, bool contains) { + if (pattern is RegExp) { + // Ideally this would also distinguish between "contains" and "equals" + // operation. + return line.contains(pattern); } return contains ? line.contains(pattern) : line == pattern; } @protected - String describe(Pattern pattern) { - if (pattern is String) { - return contains ? '"...$pattern..."' : '"$pattern"'; - } + String describe(Pattern pattern, bool contains) { if (pattern is RegExp) { return '/${pattern.pattern}/'; } @@ -66,35 +62,37 @@ abstract class Transition { } class Barrier extends Transition { - Barrier(this.pattern, {super.handler, super.logging}) : super(contains: false); - Barrier.contains(this.pattern, {super.handler, super.logging}) : super(contains: true); + Barrier(this.pattern, {super.handler, super.logging}) : contains = false; + Barrier.contains(this.pattern, {super.handler, super.logging}) : contains = true; final Pattern pattern; + final bool contains; @override - bool matches(String line) => lineMatchesPattern(line, pattern); + bool matches(String line) => lineMatchesPattern(line, pattern, contains); @override - String toString() => describe(pattern); + String toString() => describe(pattern, contains); } class Multiple extends Transition { Multiple(List patterns, {super.handler, super.logging}) : _originalPatterns = patterns, patterns = patterns.toList(), - super(contains: false); + contains = false; Multiple.contains(List patterns, {super.handler, super.logging}) : _originalPatterns = patterns, patterns = patterns.toList(), - super(contains: true); + contains = true; final List _originalPatterns; final List patterns; + final bool contains; @override bool matches(String line) { for (int index = 0; index < patterns.length; index += 1) { - if (lineMatchesPattern(line, patterns[index])) { + if (lineMatchesPattern(line, patterns[index], contains)) { patterns.removeAt(index); break; } @@ -104,7 +102,7 @@ class Multiple extends Transition { @override String toString() { - String describe(Pattern pattern) => super.describe(pattern); + String describe(Pattern pattern) => super.describe(pattern, contains); if (patterns.isEmpty) { return '${_originalPatterns.map(describe).join(', ')} (all matched)'; } From 02d779a6573cae9781f05f1968e3391b2118efde Mon Sep 17 00:00:00 2001 From: Moritz Date: Wed, 5 Mar 2025 13:46:59 +0100 Subject: [PATCH 10/25] Remove assert --- .../lib/src/isolated/native_assets/native_assets.dart | 1 - 1 file changed, 1 deletion(-) 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 97b6bdc3f2f5a..a6c1f23ef1b0e 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 @@ -127,7 +127,6 @@ Future runFlutterSpecificHooks({ }) async { final bool isWeb = targetPlatform == TargetPlatform.web_javascript; final OS? targetOS = isWeb ? null : getNativeOSFromTargetPlatform(targetPlatform!); - assert(!featureFlags.isNativeAssetsEnabled || targetOS != null); final Uri buildUri = nativeAssetsBuildUri(projectUri, isWeb ? 'web' : targetOS!.name); // Sanity check. From 9635cd0edaa49c2741f66cad239a3271615be2e6 Mon Sep 17 00:00:00 2001 From: Moritz Date: Wed, 5 Mar 2025 14:45:17 +0100 Subject: [PATCH 11/25] Wait a bit before sending the next command in tests --- .../test/integration.shard/isolated/dart_data_asset_test.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_test.dart b/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_test.dart index 0c6972507f456..f007a07482a2d 100644 --- a/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_test.dart +++ b/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_test.dart @@ -123,6 +123,7 @@ void main() { 'NOT-FOUND "packages/data_asset_example/id2".' else 'FOUND "packages/data_asset_example/id2": "content2".', + if (isWeb) 'Successful hot restart' else 'Hot restart performed', ], handler: (_) { if (!performReload) { @@ -154,6 +155,7 @@ void main() { 'FOUND "packages/data_asset_example/id3": "content3".', ], 'NOT-FOUND "packages/data_asset_example/id4".', + if (isWeb) 'Successful hot restart' else 'Hot reload performed', ], handler: (_) { return 'q'; // quit From f5a74618b17aad2874d57f93202045ab991284d4 Mon Sep 17 00:00:00 2001 From: Moritz Date: Wed, 5 Mar 2025 16:19:15 +0100 Subject: [PATCH 12/25] Add conflict error --- packages/flutter_tools/lib/src/asset.dart | 6 +++ .../isolated/dart_data_asset_test.dart | 51 +++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/packages/flutter_tools/lib/src/asset.dart b/packages/flutter_tools/lib/src/asset.dart index 162e01e2d2bb2..1c3559146f305 100644 --- a/packages/flutter_tools/lib/src/asset.dart +++ b/packages/flutter_tools/lib/src/asset.dart @@ -436,6 +436,12 @@ class ManifestAssetBundle implements AssetBundle { entryUri: Uri.parse('packages/${dataAsset.package}/${dataAsset.name}'), package: package, ); + if (assetVariants.containsKey(asset)) { + _logger.printError( + 'Conflicting asset ids: The asset $asset was declared in the pubspec and the hooks.', + ); + return 1; + } assetVariants[asset] = <_Asset>[asset]; } diff --git a/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_test.dart b/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_test.dart index f007a07482a2d..39906367e3d61 100644 --- a/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_test.dart +++ b/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_test.dart @@ -218,6 +218,57 @@ void main() { } }); } + + for (final String target in [hostOs, 'web']) { + testWithoutContext('flutter build $target with conflicting assets', () async { + final Map assets = {'id1': 'content1', 'id2': 'content2'}; + final List available = ['id1']; + writeHookLibrary(root, assets, available: available); + writeHelperLibrary(root, 'version1', assets.keys.toList()); + + final File pubspecFile = root.childFile('pubspec.yaml'); + final String content = await pubspecFile.readAsString(); + final YamlEditor yamlEditor = YamlEditor(content); + yamlEditor.update(['assets'], ['id1']); + pubspecFile.writeAsStringSync(yamlEditor.toString()); + + final ProcessTestResult result = await runFlutter( + ['build', '-v', target], + root.path, + [Barrier.contains('Built build/$target')], + debug: true, + ); + if (result.exitCode != 0) { + throw Exception( + 'flutter build failed: ${result.exitCode}\n${result.stderr}\n${result.stdout}', + ); + } + final Directory buildTargetDir = root.childDirectory('build').childDirectory(target); + + final List manifestFiles = + buildTargetDir + .listSync(recursive: true) + .whereType() + .where((File file) => file.path.endsWith('AssetManifest.json')) + .toList(); + + if (manifestFiles.isEmpty) { + throw Exception('Expected a `AssetManifest.json` to be avilable in the $buildTargetDir.'); + } + for (final File manifestFile in manifestFiles) { + final Map manifest = + json.decode(manifestFile.readAsStringSync()) as Map; + for (final String id in available) { + final String key = 'packages/$packageName/$id'; + final List entry = manifest[key]! as List; + expect(entry, equals([key])); + + final File file = manifestFile.parent.childFile(key); + expect(file.readAsStringSync(), assets[id]); + } + } + }); + } }); } From fb96bc0e1984618f0a3b68c930f5ab95411733ac Mon Sep 17 00:00:00 2001 From: Moritz Date: Thu, 6 Mar 2025 11:48:27 +0100 Subject: [PATCH 13/25] Add test for conflicting assets --- packages/flutter_tools/lib/src/asset.dart | 2 +- .../isolated/dart_data_asset_test.dart | 137 ++++++++++-------- 2 files changed, 78 insertions(+), 61 deletions(-) diff --git a/packages/flutter_tools/lib/src/asset.dart b/packages/flutter_tools/lib/src/asset.dart index 1c3559146f305..505f5ebe3b9fa 100644 --- a/packages/flutter_tools/lib/src/asset.dart +++ b/packages/flutter_tools/lib/src/asset.dart @@ -438,7 +438,7 @@ class ManifestAssetBundle implements AssetBundle { ); if (assetVariants.containsKey(asset)) { _logger.printError( - 'Conflicting asset ids: The asset $asset was declared in the pubspec and the hooks.', + 'Conflicting assets: The asset "$asset" was declared in the pubspec and the hook of package ${asset.package}.', ); return 1; } diff --git a/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_test.dart b/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_test.dart index 39906367e3d61..9d922264648e1 100644 --- a/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_test.dart +++ b/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_test.dart @@ -23,6 +23,7 @@ import 'native_assets_test_utils.dart'; final String hostOs = platform.operatingSystem; const String packageName = 'data_asset_example'; +const String packageNameDependency = 'data_asset_dependency'; void main() { if (!platform.isMacOS && !platform.isLinux && !platform.isWindows) { @@ -33,16 +34,21 @@ void main() { // various test modes. late final Directory tempDirectory; late final Directory root; + late final Directory rootDependency; setUpAll(() async { processManager.runSync([flutterBin, 'config', '--enable-native-assets']); processManager.runSync([flutterBin, 'config', '--enable-dart-data-assets']); tempDirectory = fileSystem.directory( fileSystem.systemTempDirectory.createTempSync().resolveSymbolicLinksSync(), ); - root = await createDataAssetApp(packageName, tempDirectory); + + root = createAppWithName(packageName, tempDirectory); + await createDataAssetApp(packageName, root); + + rootDependency = createAppWithName(packageNameDependency, tempDirectory); }); tearDownAll(() { - tryToDelete(tempDirectory); + // tryToDelete(tempDirectory); }); group('dart data assets', () { @@ -77,6 +83,7 @@ void main() { final bool performReload = isDebug; final Map assets = {'id1': 'content1', 'id2': 'content2'}; + writeAssets(assets, root); writeHookLibrary(root, assets, available: ['id1']); writeHelperLibrary(root, 'version1', assets.keys.toList()); @@ -104,6 +111,7 @@ void main() { // Now we trigger a hot-restart with new assets & new // application code, we make the build hook now emit also the // `id2` data asset. + writeAssets(assets, root); writeHookLibrary(root, assets, available: ['id1', 'id2']); writeHelperLibrary(root, 'version2', assets.keys.toList()); return 'R'; @@ -134,6 +142,7 @@ void main() { // `id3` data asset (but not `id4`). assets['id3'] = 'content3'; assets['id4'] = 'content4'; + writeAssets(assets, root); writeHookLibrary(root, assets, available: ['id1', 'id2', 'id3']); writeHelperLibrary(root, 'version3', assets.keys.toList()); return 'r'; @@ -178,6 +187,7 @@ void main() { testWithoutContext('flutter build $target', () async { final Map assets = {'id1': 'content1', 'id2': 'content2'}; final List available = ['id1']; + writeAssets(assets, root); writeHookLibrary(root, assets, available: available); writeHelperLibrary(root, 'version1', assets.keys.toList()); @@ -221,74 +231,62 @@ void main() { for (final String target in [hostOs, 'web']) { testWithoutContext('flutter build $target with conflicting assets', () async { - final Map assets = {'id1': 'content1', 'id2': 'content2'}; - final List available = ['id1']; + final Map assets = { + 'id1.txt': 'content1', + 'id2.txt': 'content2', + }; + final List available = ['id1.txt']; + writeAssets(assets, root); + writeAssets(assets, rootDependency); writeHookLibrary(root, assets, available: available); + writeHookLibrary(rootDependency, assets, available: available); writeHelperLibrary(root, 'version1', assets.keys.toList()); - final File pubspecFile = root.childFile('pubspec.yaml'); - final String content = await pubspecFile.readAsString(); - final YamlEditor yamlEditor = YamlEditor(content); - yamlEditor.update(['assets'], ['id1']); - pubspecFile.writeAsStringSync(yamlEditor.toString()); + await modifyPubspec(root, (YamlEditor editor) { + editor.update( + ['dependencies', packageNameDependency], + {'path': '../$packageNameDependency'}, + ); + }); + + await modifyPubspec(rootDependency, (YamlEditor editor) { + editor + ..update(['flutter', 'assets'], [assets.keys.first]) + ..update(['dependencies'], {'native_assets_cli': '^0.11.0'}); + }); final ProcessTestResult result = await runFlutter( ['build', '-v', target], root.path, - [Barrier.contains('Built build/$target')], + [ + Barrier.contains( + 'Conflicting assets: The asset "asset: packages/data_asset_dependency/id1.txt" was declared in the pubspec and the hook', + ), + ], debug: true, ); - if (result.exitCode != 0) { - throw Exception( - 'flutter build failed: ${result.exitCode}\n${result.stderr}\n${result.stdout}', - ); - } - final Directory buildTargetDir = root.childDirectory('build').childDirectory(target); - - final List manifestFiles = - buildTargetDir - .listSync(recursive: true) - .whereType() - .where((File file) => file.path.endsWith('AssetManifest.json')) - .toList(); - - if (manifestFiles.isEmpty) { - throw Exception('Expected a `AssetManifest.json` to be avilable in the $buildTargetDir.'); - } - for (final File manifestFile in manifestFiles) { - final Map manifest = - json.decode(manifestFile.readAsStringSync()) as Map; - for (final String id in available) { - final String key = 'packages/$packageName/$id'; - final List entry = manifest[key]! as List; - expect(entry, equals([key])); - - final File file = manifestFile.parent.childFile(key); - expect(file.readAsStringSync(), assets[id]); - } - } + expect(result.exitCode, isNonZero); }); } }); } -Future createDataAssetApp(String packageName, Directory tempDirectory) async { - final ProcessResult result = processManager.runSync([ - flutterBin, - 'create', - '--no-pub', - packageName, - ], workingDirectory: tempDirectory.path); - expect(result, const ProcessResultMatcher()); - - final Directory root = tempDirectory.childDirectory(packageName); - - final File pubspecFile = root.childFile('pubspec.yaml'); +Future modifyPubspec(Directory dir, void Function(YamlEditor editor) modify) async { + final File pubspecFile = dir.childFile('pubspec.yaml'); final String content = await pubspecFile.readAsString(); final YamlEditor yamlEditor = YamlEditor(content); - yamlEditor.update(['dependencies'], {'native_assets_cli': '^0.11.0'}); + modify(yamlEditor); pubspecFile.writeAsStringSync(yamlEditor.toString()); +} +Future createDataAssetApp(String packageName, Directory root) async { + await modifyPubspec( + root, + (YamlEditor editor) => + editor.update(['dependencies'], {'native_assets_cli': '^0.11.0'}), + ); + + final File pubspecFile = root.childFile('pubspec.yaml'); await pinDependencies(pubspecFile); final File mainFile = root.childDirectory('lib').childFile('main.dart'); @@ -333,8 +331,27 @@ Future createDataAssetApp(String packageName, Directory tempDirectory await processManager.run([flutterBin, 'pub', 'get'], workingDirectory: root.path), const ProcessResultMatcher(), ); +} + +Directory createAppWithName(String packageName, Directory tempDirectory) { + final ProcessResult result = processManager.runSync([ + flutterBin, + 'create', + '--no-pub', + packageName, + ], workingDirectory: tempDirectory.path); + expect(result, const ProcessResultMatcher()); + final Directory packageDirectory = tempDirectory.childDirectory(packageName); - return root; + expect( + processManager.runSync([ + flutterBin, + 'pub', + 'get', + ], workingDirectory: packageDirectory.path), + const ProcessResultMatcher(), + ); + return packageDirectory; } void writeHookLibrary( @@ -342,12 +359,6 @@ void writeHookLibrary( Map dataAssets, { required List available, }) { - final Directory assetDir = root.childDirectory('asset'); - - dataAssets.forEach((String id, String content) { - writeFile(assetDir.childFile('$id.txt'), content); - }); - final File hookFile = root.childDirectory('hook').childFile('build.dart'); available = [for (final String id in available) '"$id"']; writeFile(hookFile, ''' @@ -360,7 +371,7 @@ void writeHookLibrary( DataAsset( package: input.packageName, name: id, - file: input.packageRoot.resolve('asset/\$id.txt'), + file: input.packageRoot.resolve(id), ), ); } @@ -374,6 +385,12 @@ void writeHookLibrary( '''); } +void writeAssets(Map dataAssets, Directory root) { + dataAssets.forEach((String id, String content) { + writeFile(root.childFile(id), content); + }); +} + void writeHelperLibrary(Directory root, String version, List assetIds) { assetIds = [for (final String id in assetIds) '"packages/$packageName/$id"']; final File helperFile = root.childDirectory('lib').childFile('helper.dart'); From 9fdc55f4ac360f1d7b0648bcc9f246745cff1ba1 Mon Sep 17 00:00:00 2001 From: Moritz Date: Thu, 6 Mar 2025 11:50:25 +0100 Subject: [PATCH 14/25] cleanup --- packages/flutter_tools/lib/src/asset.dart | 2 +- .../test/integration.shard/isolated/dart_data_asset_test.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/flutter_tools/lib/src/asset.dart b/packages/flutter_tools/lib/src/asset.dart index 505f5ebe3b9fa..47a5ac7c95803 100644 --- a/packages/flutter_tools/lib/src/asset.dart +++ b/packages/flutter_tools/lib/src/asset.dart @@ -438,7 +438,7 @@ class ManifestAssetBundle implements AssetBundle { ); if (assetVariants.containsKey(asset)) { _logger.printError( - 'Conflicting assets: The asset "$asset" was declared in the pubspec and the hook of package ${asset.package}.', + 'Conflicting assets: The asset "$asset" was declared in the pubspec and the hook.', ); return 1; } diff --git a/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_test.dart b/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_test.dart index 9d922264648e1..69b321c5e0484 100644 --- a/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_test.dart +++ b/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_test.dart @@ -48,7 +48,7 @@ void main() { rootDependency = createAppWithName(packageNameDependency, tempDirectory); }); tearDownAll(() { - // tryToDelete(tempDirectory); + tryToDelete(tempDirectory); }); group('dart data assets', () { From 12141d649ee45dfd1d75d4351b5616326fa4367e Mon Sep 17 00:00:00 2001 From: Moritz Date: Thu, 6 Mar 2025 12:57:43 +0100 Subject: [PATCH 15/25] Add setup/teardown for all tests individually --- .../integration.shard/isolated/dart_data_asset_test.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_test.dart b/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_test.dart index 69b321c5e0484..ea6c2e8c72239 100644 --- a/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_test.dart +++ b/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_test.dart @@ -38,16 +38,19 @@ void main() { setUpAll(() async { processManager.runSync([flutterBin, 'config', '--enable-native-assets']); processManager.runSync([flutterBin, 'config', '--enable-dart-data-assets']); + }); + + setUp(() async { tempDirectory = fileSystem.directory( fileSystem.systemTempDirectory.createTempSync().resolveSymbolicLinksSync(), ); - root = createAppWithName(packageName, tempDirectory); await createDataAssetApp(packageName, root); rootDependency = createAppWithName(packageNameDependency, tempDirectory); }); - tearDownAll(() { + + tearDown(() { tryToDelete(tempDirectory); }); From fbf37fce04d580134dcd850244482458779b36db Mon Sep 17 00:00:00 2001 From: Moritz Date: Fri, 7 Mar 2025 09:15:37 +0100 Subject: [PATCH 16/25] Fix test setup --- .../isolated/dart_data_asset_test.dart | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_test.dart b/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_test.dart index ea6c2e8c72239..fd088be9fa8da 100644 --- a/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_test.dart +++ b/packages/flutter_tools/test/integration.shard/isolated/dart_data_asset_test.dart @@ -29,18 +29,17 @@ void main() { if (!platform.isMacOS && !platform.isLinux && !platform.isWindows) { return; } - - // Create project structure once as we can re-use this for executing the - // various test modes. - late final Directory tempDirectory; - late final Directory root; - late final Directory rootDependency; setUpAll(() async { processManager.runSync([flutterBin, 'config', '--enable-native-assets']); processManager.runSync([flutterBin, 'config', '--enable-dart-data-assets']); }); + late Directory tempDirectory; + late Directory root; + late Directory rootDependency; + setUp(() async { + // Do not reuse project structure to be able to make local changes tempDirectory = fileSystem.directory( fileSystem.systemTempDirectory.createTempSync().resolveSymbolicLinksSync(), ); From 8a8932e591eb4dee7961c27e50613ded52f2368e Mon Sep 17 00:00:00 2001 From: Moritz Date: Thu, 6 Mar 2025 17:29:06 +0100 Subject: [PATCH 17/25] pass artifact to flutter config --- .../build_system/targets/native_assets.dart | 4 +- .../native_assets/flutter_config.dart | 56 +++++++++++++++++++ .../isolated/native_assets/native_assets.dart | 34 +++++++---- 3 files changed, 81 insertions(+), 13 deletions(-) create mode 100644 packages/flutter_tools/lib/src/isolated/native_assets/flutter_config.dart 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 e187f903d4126..0cb7fa449f416 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'; @@ -55,7 +56,7 @@ class DartBuild extends Target { runPackageName!, ); result = await runFlutterSpecificHooks( - environmentDefines: environment.defines, + environment: environment, buildRunner: buildRunner, targetPlatform: targetPlatform, projectUri: projectUri, @@ -183,6 +184,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/isolated/native_assets/flutter_config.dart b/packages/flutter_tools/lib/src/isolated/native_assets/flutter_config.dart new file mode 100644 index 0000000000000..a54c877fec6d8 --- /dev/null +++ b/packages/flutter_tools/lib/src/isolated/native_assets/flutter_config.dart @@ -0,0 +1,56 @@ +// 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/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart b/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart index a6c1f23ef1b0e..23384e798b6c8 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 @@ -10,11 +10,13 @@ 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/build_system.dart' show Environment; import '../../build_system/exceptions.dart'; import '../../cache.dart'; import '../../convert.dart'; @@ -22,6 +24,7 @@ import '../../features.dart'; import '../../globals.dart' as globals; import '../../macos/xcode.dart' as xcode; import 'android/native_assets.dart'; +import 'flutter_config.dart'; import 'ios/native_assets.dart'; import 'linux/native_assets.dart'; import 'macos/native_assets.dart'; @@ -119,7 +122,7 @@ final class DartHookResult { /// Invokes the build of all transitive Dart package hooks and prepares assets /// to be included in the native build. Future runFlutterSpecificHooks({ - required Map environmentDefines, + required Environment environment, required FlutterNativeAssetsBuildRunner buildRunner, required TargetPlatform? targetPlatform, required Uri projectUri, @@ -130,6 +133,7 @@ Future runFlutterSpecificHooks({ final Uri buildUri = nativeAssetsBuildUri(projectUri, isWeb ? 'web' : targetOS!.name); // Sanity check. + final Map environmentDefines = environment.defines; final String? codesignIdentity = environmentDefines[kCodesignIdentity]; assert(codesignIdentity == null || targetOS == OS.iOS || targetOS == OS.macOS); @@ -158,7 +162,7 @@ Future runFlutterSpecificHooks({ architectures?.isEmpty ?? false ? DartHookResult.empty() : await _runDartHooks( - environmentDefines: environmentDefines, + environment: environment, buildRunner: buildRunner, codeAssetSupport: !isWeb && featureFlags.isNativeAssetsEnabled, dataAssetSupport: featureFlags.isDartDataAssetsEnabled, @@ -618,7 +622,7 @@ Future _copyNativeCodeAssetsForOS( /// This will invoke `hook/build.dart` and `hook/link.dart` (if applicable) for /// all transitive dart packages that define such hooks. Future _runDartHooks({ - required Map environmentDefines, + required Environment environment, required FlutterNativeAssetsBuildRunner buildRunner, required List? architectures, required Uri projectUri, @@ -643,6 +647,7 @@ Future _runDartHooks({ final List assets = []; final Set dependencies = {}; + final Map environmentDefines = environment.defines; final EnvironmentType? environmentType; if (targetOS == OS.iOS) { final String? sdkRoot = environmentDefines[kSdkRoot]; @@ -682,15 +687,20 @@ Future _runDartHooks({ inputCreator: () { final BuildInputBuilder buildInputBuilder = BuildInputBuilder(); if (targetOS != null) { - buildInputBuilder.config.setupCode( - targetArchitecture: architecture, - linkModePreference: LinkModePreference.dynamic, - cCompiler: cCompilerConfig, - targetOS: targetOS, - android: androidConfig, - iOS: iosConfig, - macOS: macOSConfig, - ); + buildInputBuilder.config + ..setupCode( + targetArchitecture: architecture, + linkModePreference: LinkModePreference.dynamic, + cCompiler: cCompilerConfig, + targetOS: targetOS, + android: androidConfig, + iOS: iosConfig, + macOS: macOSConfig, + ) + ..setupFlutter( + //TODO(mosum): In the future, decouple the font subset tool from Flutter by shipping it with the icon treeshaking package. + fontSubsetBinary: environment.artifacts.getArtifactPath(Artifact.fontSubset), + ); } return buildInputBuilder; }, From 5382795e07db0eee7ddf4c92547587bdbb453e57 Mon Sep 17 00:00:00 2001 From: Moritz Date: Thu, 6 Mar 2025 17:31:10 +0100 Subject: [PATCH 18/25] Add icon treeshaker --- dev/icon_treeshaker/.gitignore | 7 + dev/icon_treeshaker/CHANGELOG.md | 3 + dev/icon_treeshaker/README.md | 39 +++++ dev/icon_treeshaker/analysis_options.yaml | 30 ++++ .../example/icon_treeshaker_example.dart | 6 + .../example_flutter/.gitignore | 45 ++++++ dev/icon_treeshaker/example_flutter/.metadata | 30 ++++ dev/icon_treeshaker/example_flutter/README.md | 3 + .../example_flutter/analysis_options.yaml | 1 + .../example_flutter/lib/main.dart | 16 ++ .../example_flutter/linux/.gitignore | 1 + .../example_flutter/linux/CMakeLists.txt | 128 ++++++++++++++++ .../linux/flutter/CMakeLists.txt | 88 +++++++++++ .../linux/runner/CMakeLists.txt | 26 ++++ .../example_flutter/linux/runner/main.cc | 6 + .../linux/runner/my_application.cc | 130 ++++++++++++++++ .../linux/runner/my_application.h | 18 +++ .../example_flutter/pubspec.yaml | 19 +++ dev/icon_treeshaker/hook/link.dart | 130 ++++++++++++++++ dev/icon_treeshaker/lib/hook_helper.dart | 131 ++++++++++++++++ dev/icon_treeshaker/lib/icon_treeshaker.dart | 8 + .../lib/src/icon_treeshaker_base.dart | 144 ++++++++++++++++++ dev/icon_treeshaker/pubspec.yaml | 22 +++ .../test/icon_treeshaker_test.dart | 16 ++ 24 files changed, 1047 insertions(+) create mode 100644 dev/icon_treeshaker/.gitignore create mode 100644 dev/icon_treeshaker/CHANGELOG.md create mode 100644 dev/icon_treeshaker/README.md create mode 100644 dev/icon_treeshaker/analysis_options.yaml create mode 100644 dev/icon_treeshaker/example/icon_treeshaker_example.dart create mode 100644 dev/icon_treeshaker/example_flutter/.gitignore create mode 100644 dev/icon_treeshaker/example_flutter/.metadata create mode 100644 dev/icon_treeshaker/example_flutter/README.md create mode 100644 dev/icon_treeshaker/example_flutter/analysis_options.yaml create mode 100644 dev/icon_treeshaker/example_flutter/lib/main.dart create mode 100644 dev/icon_treeshaker/example_flutter/linux/.gitignore create mode 100644 dev/icon_treeshaker/example_flutter/linux/CMakeLists.txt create mode 100644 dev/icon_treeshaker/example_flutter/linux/flutter/CMakeLists.txt create mode 100644 dev/icon_treeshaker/example_flutter/linux/runner/CMakeLists.txt create mode 100644 dev/icon_treeshaker/example_flutter/linux/runner/main.cc create mode 100644 dev/icon_treeshaker/example_flutter/linux/runner/my_application.cc create mode 100644 dev/icon_treeshaker/example_flutter/linux/runner/my_application.h create mode 100644 dev/icon_treeshaker/example_flutter/pubspec.yaml create mode 100644 dev/icon_treeshaker/hook/link.dart create mode 100644 dev/icon_treeshaker/lib/hook_helper.dart create mode 100644 dev/icon_treeshaker/lib/icon_treeshaker.dart create mode 100644 dev/icon_treeshaker/lib/src/icon_treeshaker_base.dart create mode 100644 dev/icon_treeshaker/pubspec.yaml create mode 100644 dev/icon_treeshaker/test/icon_treeshaker_test.dart 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..1ee5256101fde --- /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: "1feb1f76cc94a31413927ab3d467f14c6a1fd281" + channel: "[user-branch]" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 1feb1f76cc94a31413927ab3d467f14c6a1fd281 + base_revision: 1feb1f76cc94a31413927ab3d467f14c6a1fd281 + - platform: linux + create_revision: 1feb1f76cc94a31413927ab3d467f14c6a1fd281 + base_revision: 1feb1f76cc94a31413927ab3d467f14c6a1fd281 + + # 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/lib/main.dart b/dev/icon_treeshaker/example_flutter/lib/main.dart new file mode 100644 index 0000000000000..be11ab2667eb8 --- /dev/null +++ b/dev/icon_treeshaker/example_flutter/lib/main.dart @@ -0,0 +1,16 @@ +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: Icon(Icons.abc_outlined))), + ); + } +} 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..28d99d86650aa --- /dev/null +++ b/dev/icon_treeshaker/example_flutter/pubspec.yaml @@ -0,0 +1,19 @@ +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 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + +flutter: + uses-material-design: true diff --git a/dev/icon_treeshaker/hook/link.dart b/dev/icon_treeshaker/hook/link.dart new file mode 100644 index 0000000000000..90140e0de344d --- /dev/null +++ b/dev/icon_treeshaker/hook/link.dart @@ -0,0 +1,130 @@ +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/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 pathToSubsetBinary = ''; + 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 $pathToSubsetBinary on $fontFile.'); + final success = await subsetFont( + fontFile: fontFile, + outputPath: p.join( + input.outputDirectoryShared.path, + //TODO(mosum): Handle duplicates + p.basename(fontAsset.file), + ), + iconData: iconData, + fs: fs, + pathToSubsetBinary: pathToSubsetBinary, + logger: logger, + ); + if (!success) { + throw io.ProcessException( + pathToSubsetBinary, + [], + '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, + relativePath: font.file, + 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/hook_helper.dart b/dev/icon_treeshaker/lib/hook_helper.dart new file mode 100644 index 0000000000000..12c35ddfbd8e0 --- /dev/null +++ b/dev/icon_treeshaker/lib/hook_helper.dart @@ -0,0 +1,131 @@ +// 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:logging/logging.dart' show Logger; +import 'package:native_assets_cli/data_assets.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; + +class FontBuilder { + final List fonts = []; + + FontBuilder(); + + void addFont(String family, String style, int weight, String file) => + fonts.add( + (input) => FontAsset( + family: family, + style: style, + weight: weight, + file: file, + package: input.packageName, + ), + ); + + Future run({ + required BuildInput input, + required BuildOutputBuilder output, + Logger? logger, + String? linkInPackage = 'assets', + }) async { + output.assets.addEncodedAssets( + fonts.map((font) => font(input).encode()), + linkInPackage: linkInPackage, + ); + } +} + +/// 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 String file; + + /// The package which contains this asset. + final String package; + + /// The identifier for this data asset. + /// + /// An [DataAsset] has a string identifier called "asset id". Dart code that + /// uses an asset references the asset using this asset id. + /// + /// An asset identifier consists of two elements, the `package` and `name`, + /// which together make a library uri `package:/`. The package + /// being part of the identifer prevents name collisions between assets of + /// different packages. + String get id => 'package:$package/$file'; + + /// 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: 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, + _familyKey: family, + _styleKey: style, + _weightKey: weight, + }), + ); + + @override + String toString() => 'FontAsset(${encode().encoding})'; + + static const String type = 'icon'; + + FontAsset({ + required this.family, + required this.style, + required this.weight, + required this.file, + required this.package, + }); +} + +const _familyKey = 'family'; +const _styleKey = 'style'; +const _weightKey = 'weight'; +const _packageKey = 'package'; +const _fileKey = 'file'; 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..c46a9a1b22d39 --- /dev/null +++ b/dev/icon_treeshaker/lib/src/icon_treeshaker_base.dart @@ -0,0 +1,144 @@ +// 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.relativePath, + required this.codePoints, + required this.optionalCodePoints, + }); + + /// The font family name, e.g. "MaterialIcons". + final String family; + + /// The relative path to the font file. + final String relativePath; + + /// 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, $relativePath, $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); + }); + }); +} From 2a386ef0b900f4769f33cbc6cdc447eebd201ebb Mon Sep 17 00:00:00 2001 From: Moritz Date: Fri, 7 Mar 2025 11:09:13 +0100 Subject: [PATCH 19/25] Add config to package --- dev/icon_treeshaker/hook/link.dart | 26 +++-- .../icon_treeshaker/lib}/flutter_config.dart | 25 ++++- dev/icon_treeshaker/lib/hook_helper.dart | 97 +++++++++++-------- .../build_system/targets/native_assets.dart | 5 +- .../isolated/native_assets/native_assets.dart | 13 +-- .../native_assets/test/native_assets.dart | 2 + packages/flutter_tools/pubspec.yaml | 2 + 7 files changed, 106 insertions(+), 64 deletions(-) rename {packages/flutter_tools/lib/src/isolated/native_assets => dev/icon_treeshaker/lib}/flutter_config.dart (76%) diff --git a/dev/icon_treeshaker/hook/link.dart b/dev/icon_treeshaker/hook/link.dart index 90140e0de344d..8122ac7739ed1 100644 --- a/dev/icon_treeshaker/hook/link.dart +++ b/dev/icon_treeshaker/hook/link.dart @@ -4,6 +4,7 @@ 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'; @@ -21,7 +22,12 @@ Future main(List arguments) async => await link(arguments, ( FileSystem fs = LocalFileSystem(); var usages = input.usages; //TODO(mosum): Get path to subset from flutter - final pathToSubsetBinary = ''; + 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, @@ -35,22 +41,24 @@ Future main(List arguments) async => await link(arguments, ( continue; } final fontFile = fs.file(fontAsset.file); - logger.info('Running $pathToSubsetBinary on $fontFile.'); + logger.info('Running $fontSubset on $fontFile.'); + var pathOfSubsetFont = p.join( + input.outputDirectoryShared.path, + //TODO(mosum): Handle duplicates + p.basename(fontAsset.file), + ); final success = await subsetFont( fontFile: fontFile, - outputPath: p.join( - input.outputDirectoryShared.path, - //TODO(mosum): Handle duplicates - p.basename(fontAsset.file), - ), + outputPath: pathOfSubsetFont, iconData: iconData, fs: fs, - pathToSubsetBinary: pathToSubsetBinary, + pathToSubsetBinary: fontSubset, logger: logger, ); + output.assets.font.add(fontAsset.copyWith(file: pathOfSubsetFont)); if (!success) { throw io.ProcessException( - pathToSubsetBinary, + fontSubset, [], 'Failure when trying to treeshake $fontAsset. Check the log for more details.', ); diff --git a/packages/flutter_tools/lib/src/isolated/native_assets/flutter_config.dart b/dev/icon_treeshaker/lib/flutter_config.dart similarity index 76% rename from packages/flutter_tools/lib/src/isolated/native_assets/flutter_config.dart rename to dev/icon_treeshaker/lib/flutter_config.dart index a54c877fec6d8..0f02e2bc19dfd 100644 --- a/packages/flutter_tools/lib/src/isolated/native_assets/flutter_config.dart +++ b/dev/icon_treeshaker/lib/flutter_config.dart @@ -15,11 +15,16 @@ extension FlutterHookConfig on HookConfig { class FlutterConfig { factory FlutterConfig.fromJson(Map json) { return FlutterConfig._( - fontSubsetBinary: json.getNested([_configKey, _flutterKey, _fontSubsetBinaryKey]), + 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; + FlutterConfig._({String? fontSubsetBinary}) + : _fontSubsetBinary = fontSubsetBinary; final String? _fontSubsetBinary; @@ -29,7 +34,11 @@ class FlutterConfig { /// Extension to initialize code specific configuration on link/build inputs. extension CodeAssetBuildInputBuilder on HookConfigBuilder { void setupFlutter({String? fontSubsetBinary}) { - json.setNested([_configKey, _flutterKey, _fontSubsetBinaryKey], fontSubsetBinary); + json.setNested([ + _configKey, + _flutterKey, + _fontSubsetBinaryKey, + ], fontSubsetBinary); } } @@ -40,7 +49,10 @@ 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)) { + for (final String key in nestedMapKeys.sublist( + 0, + nestedMapKeys.length - 1, + )) { map = (map[key] ??= {}) as Map; } map[nestedMapKeys.last] = value; @@ -48,7 +60,10 @@ extension MapJsonUtils on Map { T? getNested(List nestedMapKeys) { Map map = this; - for (final String key in nestedMapKeys.sublist(0, nestedMapKeys.length - 1)) { + 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 index 12c35ddfbd8e0..ead154c97b7e5 100644 --- a/dev/icon_treeshaker/lib/hook_helper.dart +++ b/dev/icon_treeshaker/lib/hook_helper.dart @@ -4,39 +4,9 @@ import 'dart:collection'; -import 'package:logging/logging.dart' show Logger; import 'package:native_assets_cli/data_assets.dart'; import 'package:native_assets_cli/native_assets_cli.dart'; -class FontBuilder { - final List fonts = []; - - FontBuilder(); - - void addFont(String family, String style, int weight, String file) => - fonts.add( - (input) => FontAsset( - family: family, - style: style, - weight: weight, - file: file, - package: input.packageName, - ), - ); - - Future run({ - required BuildInput input, - required BuildOutputBuilder output, - Logger? logger, - String? linkInPackage = 'assets', - }) async { - output.assets.addEncodedAssets( - fonts.map((font) => font(input).encode()), - linkInPackage: linkInPackage, - ); - } -} - /// Data bundled with a Dart or Flutter application. /// /// A data asset is accessible in a Dart or Flutter application. To retrieve an @@ -49,7 +19,7 @@ final class FontAsset { final String family; /// https://api.flutter.dev/flutter/dart-ui/FontStyle.html values - italic or normal - final String style; + final String? style; /// integer multiple of 100, between 100 and 900 final int? weight; @@ -61,15 +31,8 @@ final class FontAsset { final String package; /// The identifier for this data asset. - /// - /// An [DataAsset] has a string identifier called "asset id". Dart code that - /// uses an asset references the asset using this asset id. - /// - /// An asset identifier consists of two elements, the `package` and `name`, - /// which together make a library uri `package:/`. The package - /// being part of the identifer prevents name collisions between assets of - /// different packages. - String get id => 'package:$package/$file'; + /// 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) { @@ -77,7 +40,7 @@ final class FontAsset { final jsonMap = asset.encoding; return FontAsset( family: jsonMap[_familyKey] as String, - style: jsonMap[_styleKey] as String, + style: jsonMap[_styleKey] as String?, weight: jsonMap[_weightKey] as int?, package: jsonMap[_packageKey] as String, file: jsonMap[_fileKey] as String, @@ -122,6 +85,20 @@ final class FontAsset { required this.file, required this.package, }); + + FontAsset copyWith({ + String? family, + String? style, + int? weight, + String? 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'; @@ -129,3 +106,41 @@ 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/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart b/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart index 0cb7fa449f416..2620c5a652644 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 @@ -56,7 +56,10 @@ class DartBuild extends Target { runPackageName!, ); result = await runFlutterSpecificHooks( - environment: environment, + environmentDefines: { + ...environment.defines, + Artifact.fontSubset.name: environment.artifacts.getArtifactPath(Artifact.fontSubset), + }, buildRunner: buildRunner, targetPlatform: targetPlatform, projectUri: projectUri, 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 23384e798b6c8..d7af9d6c05192 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,6 +4,7 @@ // Logic for native assets shared between all host OSes. +import 'package:icon_treeshaker/flutter_config.dart'; 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'; @@ -16,7 +17,6 @@ import '../../base/file_system.dart'; import '../../base/logger.dart'; import '../../base/platform.dart'; import '../../build_info.dart'; -import '../../build_system/build_system.dart' show Environment; import '../../build_system/exceptions.dart'; import '../../cache.dart'; import '../../convert.dart'; @@ -24,7 +24,6 @@ import '../../features.dart'; import '../../globals.dart' as globals; import '../../macos/xcode.dart' as xcode; import 'android/native_assets.dart'; -import 'flutter_config.dart'; import 'ios/native_assets.dart'; import 'linux/native_assets.dart'; import 'macos/native_assets.dart'; @@ -122,7 +121,7 @@ final class DartHookResult { /// Invokes the build of all transitive Dart package hooks and prepares assets /// to be included in the native build. Future runFlutterSpecificHooks({ - required Environment environment, + required Map environmentDefines, required FlutterNativeAssetsBuildRunner buildRunner, required TargetPlatform? targetPlatform, required Uri projectUri, @@ -133,7 +132,6 @@ Future runFlutterSpecificHooks({ final Uri buildUri = nativeAssetsBuildUri(projectUri, isWeb ? 'web' : targetOS!.name); // Sanity check. - final Map environmentDefines = environment.defines; final String? codesignIdentity = environmentDefines[kCodesignIdentity]; assert(codesignIdentity == null || targetOS == OS.iOS || targetOS == OS.macOS); @@ -162,7 +160,7 @@ Future runFlutterSpecificHooks({ architectures?.isEmpty ?? false ? DartHookResult.empty() : await _runDartHooks( - environment: environment, + environmentDefines: environmentDefines, buildRunner: buildRunner, codeAssetSupport: !isWeb && featureFlags.isNativeAssetsEnabled, dataAssetSupport: featureFlags.isDartDataAssetsEnabled, @@ -622,7 +620,7 @@ Future _copyNativeCodeAssetsForOS( /// This will invoke `hook/build.dart` and `hook/link.dart` (if applicable) for /// all transitive dart packages that define such hooks. Future _runDartHooks({ - required Environment environment, + required Map environmentDefines, required FlutterNativeAssetsBuildRunner buildRunner, required List? architectures, required Uri projectUri, @@ -647,7 +645,6 @@ Future _runDartHooks({ final List assets = []; final Set dependencies = {}; - final Map environmentDefines = environment.defines; final EnvironmentType? environmentType; if (targetOS == OS.iOS) { final String? sdkRoot = environmentDefines[kSdkRoot]; @@ -699,7 +696,7 @@ Future _runDartHooks({ ) ..setupFlutter( //TODO(mosum): In the future, decouple the font subset tool from Flutter by shipping it with the icon treeshaking package. - fontSubsetBinary: environment.artifacts.getArtifactPath(Artifact.fontSubset), + fontSubsetBinary: environmentDefines[Artifact.fontSubset.name], ); } return buildInputBuilder; diff --git a/packages/flutter_tools/lib/src/isolated/native_assets/test/native_assets.dart b/packages/flutter_tools/lib/src/isolated/native_assets/test/native_assets.dart index 46d5801c02f7c..458ab53ba09eb 100644 --- a/packages/flutter_tools/lib/src/isolated/native_assets/test/native_assets.dart +++ b/packages/flutter_tools/lib/src/isolated/native_assets/test/native_assets.dart @@ -7,6 +7,7 @@ import 'package:native_assets_cli/code_assets.dart' show OS; import 'package:package_config/package_config_types.dart'; +import '../../../artifacts.dart' show Artifact; import '../../../base/platform.dart'; import '../../../build_info.dart'; import '../../../globals.dart' as globals; @@ -59,6 +60,7 @@ Future testCompilerBuildNativeAssets(BuildInfo buildInfo) async { final Map environmentDefines = { kBuildMode: buildInfo.mode.cliName, + Artifact.fontSubset.name: '', }; // First perform the dart build. diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml index ec243584e9913..e29dabb3e1b12 100644 --- a/packages/flutter_tools/pubspec.yaml +++ b/packages/flutter_tools/pubspec.yaml @@ -57,6 +57,8 @@ dependencies: graphs: 2.3.2 native_assets_builder: 0.11.1 native_assets_cli: 0.11.0 + icon_treeshaker: + path: ../../dev/icon_treeshaker # We depend on very specific internal implementation details of the # 'test' package, which change between versions, so when upgrading From 4048d66cc5089164c175f64d45e82a81d8aa43f9 Mon Sep 17 00:00:00 2001 From: Moritz Date: Fri, 7 Mar 2025 11:30:33 +0100 Subject: [PATCH 20/25] add icons --- .../example_flutter/fonts/TwoIcons.ttf | Bin 0 -> 2000 bytes .../example_flutter/lib/main.dart | 8 +++++++- dev/icon_treeshaker/example_flutter/pubspec.yaml | 5 +++++ 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 dev/icon_treeshaker/example_flutter/fonts/TwoIcons.ttf 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 0000000000000000000000000000000000000000..788e667c17220436c53dbcb4f870261c48c98583 GIT binary patch literal 2000 zcmd^A&rcgi6n^71Ho*v@CWa{Lk5z53DUJb~#ZfSb;$jy;XzDnQM1Q0kv-TRTcdhk0 z;7DzABT`Y+1I?+YR;koWjQXJe?=9kJ^0W=F1k=}g_6IZvzj;G``$M* zZ=T1y#E2+Bw@9Mt+~q6F@;75QiP(9tg^w;zB^Kq?UHJR(i=~=czn%H(XZX+2zp5Cm z^5K(&LnLv{o4TfMz1N<*4ec}ZIUR&QEfs+uqKEaGvm5voh}fSngN9X7X_@X|ejR>) zP2H`tVOl`^41C#CYubn3{{rYefQIT;!@2kM!ykzJ@F`8mUCJMA!?xjb%TLqWB$omE zbmhT=Ys2ZkDbUgW@w|1F3p?Y{Z&F)I!R;rHn9f&{Xh!&)9l%3$)Ds_|X<8)D2if%k z4H>Fq(pmEM#CtI_sudOM<8W^Gx=IFyk*~{2ZT2no5qWNUc*XWRBYQ?O3>kQ+PXhaj zcB#CwMN4gJGq)a_Ql9QYCaS?t-0fhoK2AmyLq~?JD0G*=@)2cCau5~5&e)E@~2 zVS*vWA4w@8n2?f;q(+kpjFJqdM*YDgjC62+Z~jr?ae6PmmwsG$G{1NMIKf^Yda31k zARpwgjGKQFH<`u~i%gk~`20Tq5DT7uo1GdN9SNR>>w7mc9vN3=Q*$ZKcXZ`tW3KVj?#gogM4c@D2K%SIBKy*be6ZmaM=Y^3#UE68I|u zdx1X_xDRhOO<*7Ry1)ad`L4hLIz>P7o9{h=efH^4U`8j|vcMi1VtIij@Lve*1+EL+ zMDolxZKE!hH9HC01an~rYT4S7i} z8%@X2>}ts}<0Y#WlgpOrXog{NudrjSBGAZdm8PNEUAH?HHM>!@OnD|g-KBDxsoAQd zZSn0IH!2CoDa&Qss>xZgu3WdRZLQ?Qb;qe+n40R{k1}MTel4=8N)^({A(^6JCom>( zdlIAoU57%3vZJb!Nh%p&RBB>|?nWB$m(a_IaMrM z%*$?;Hdd+uW!)A3%U4Flcx)T%l%S8hb#uU8pea1BJ74!b!M``JY7+A>iFwJdmaa9; Ps?)!tt4>3=@Fo5iuOvOz literal 0 HcmV?d00001 diff --git a/dev/icon_treeshaker/example_flutter/lib/main.dart b/dev/icon_treeshaker/example_flutter/lib/main.dart index be11ab2667eb8..5a8bdf8e11434 100644 --- a/dev/icon_treeshaker/example_flutter/lib/main.dart +++ b/dev/icon_treeshaker/example_flutter/lib/main.dart @@ -10,7 +10,13 @@ class MainApp extends StatelessWidget { @override Widget build(BuildContext context) { return const MaterialApp( - home: Scaffold(body: Center(child: Icon(Icons.abc_outlined))), + home: Scaffold(body: Center(child: 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/pubspec.yaml b/dev/icon_treeshaker/example_flutter/pubspec.yaml index 28d99d86650aa..4e4bc6db4d0ff 100644 --- a/dev/icon_treeshaker/example_flutter/pubspec.yaml +++ b/dev/icon_treeshaker/example_flutter/pubspec.yaml @@ -17,3 +17,8 @@ dev_dependencies: flutter: uses-material-design: true + + fonts: + - family: TwoIcons + fonts: + - asset: fonts/TwoIcons.ttf From cc9d24033423bcf9b24666e1de7c030bb5741c7a Mon Sep 17 00:00:00 2001 From: Moritz Date: Fri, 7 Mar 2025 15:46:36 +0100 Subject: [PATCH 21/25] Add android --- .../example_flutter/android/.gitignore | 14 ++++++ .../android/app/build.gradle.kts | 44 +++++++++++++++++ .../android/app/src/debug/AndroidManifest.xml | 7 +++ .../android/app/src/main/AndroidManifest.xml | 45 ++++++++++++++++++ .../example/example_flutter/MainActivity.kt | 5 ++ .../res/drawable-v21/launch_background.xml | 12 +++++ .../main/res/drawable/launch_background.xml | 12 +++++ .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 +++++++ .../app/src/main/res/values/styles.xml | 18 +++++++ .../app/src/profile/AndroidManifest.xml | 7 +++ .../example_flutter/android/build.gradle.kts | 21 ++++++++ .../example_flutter/android/gradle.properties | 3 ++ .../gradle/wrapper/gradle-wrapper.properties | 5 ++ .../android/settings.gradle.kts | 25 ++++++++++ 19 files changed, 236 insertions(+) create mode 100644 dev/icon_treeshaker/example_flutter/android/.gitignore create mode 100644 dev/icon_treeshaker/example_flutter/android/app/build.gradle.kts create mode 100644 dev/icon_treeshaker/example_flutter/android/app/src/debug/AndroidManifest.xml create mode 100644 dev/icon_treeshaker/example_flutter/android/app/src/main/AndroidManifest.xml create mode 100644 dev/icon_treeshaker/example_flutter/android/app/src/main/kotlin/com/example/example_flutter/MainActivity.kt create mode 100644 dev/icon_treeshaker/example_flutter/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 dev/icon_treeshaker/example_flutter/android/app/src/main/res/drawable/launch_background.xml create mode 100644 dev/icon_treeshaker/example_flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 dev/icon_treeshaker/example_flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 dev/icon_treeshaker/example_flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 dev/icon_treeshaker/example_flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 dev/icon_treeshaker/example_flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 dev/icon_treeshaker/example_flutter/android/app/src/main/res/values-night/styles.xml create mode 100644 dev/icon_treeshaker/example_flutter/android/app/src/main/res/values/styles.xml create mode 100644 dev/icon_treeshaker/example_flutter/android/app/src/profile/AndroidManifest.xml create mode 100644 dev/icon_treeshaker/example_flutter/android/build.gradle.kts create mode 100644 dev/icon_treeshaker/example_flutter/android/gradle.properties create mode 100644 dev/icon_treeshaker/example_flutter/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 dev/icon_treeshaker/example_flutter/android/settings.gradle.kts 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 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 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") From 81944014bbcfbdf5a9d08b4d810e032a99a46134 Mon Sep 17 00:00:00 2001 From: Moritz Date: Fri, 7 Mar 2025 15:46:59 +0100 Subject: [PATCH 22/25] fixes --- dev/icon_treeshaker/example_flutter/.metadata | 12 ++++---- .../example_flutter/hook/build.dart | 14 +++++++++ .../example_flutter/lib/main.dart | 6 +++- .../example_flutter/pubspec.yaml | 3 ++ .../example_flutter/test/widget_test.dart | 30 +++++++++++++++++++ dev/icon_treeshaker/lib/hook_helper.dart | 20 ++++++------- .../isolated/native_assets/native_assets.dart | 29 ++++++++++++++++-- 7 files changed, 95 insertions(+), 19 deletions(-) create mode 100644 dev/icon_treeshaker/example_flutter/hook/build.dart create mode 100644 dev/icon_treeshaker/example_flutter/test/widget_test.dart diff --git a/dev/icon_treeshaker/example_flutter/.metadata b/dev/icon_treeshaker/example_flutter/.metadata index 1ee5256101fde..dac569ea96e9a 100644 --- a/dev/icon_treeshaker/example_flutter/.metadata +++ b/dev/icon_treeshaker/example_flutter/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "1feb1f76cc94a31413927ab3d467f14c6a1fd281" + revision: "4048d66cc5089164c175f64d45e82a81d8aa43f9" channel: "[user-branch]" project_type: app @@ -13,11 +13,11 @@ project_type: app migration: platforms: - platform: root - create_revision: 1feb1f76cc94a31413927ab3d467f14c6a1fd281 - base_revision: 1feb1f76cc94a31413927ab3d467f14c6a1fd281 - - platform: linux - create_revision: 1feb1f76cc94a31413927ab3d467f14c6a1fd281 - base_revision: 1feb1f76cc94a31413927ab3d467f14c6a1fd281 + create_revision: 4048d66cc5089164c175f64d45e82a81d8aa43f9 + base_revision: 4048d66cc5089164c175f64d45e82a81d8aa43f9 + - platform: android + create_revision: 4048d66cc5089164c175f64d45e82a81d8aa43f9 + base_revision: 4048d66cc5089164c175f64d45e82a81d8aa43f9 # User provided section 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 index 5a8bdf8e11434..bf5ed1ef5d197 100644 --- a/dev/icon_treeshaker/example_flutter/lib/main.dart +++ b/dev/icon_treeshaker/example_flutter/lib/main.dart @@ -10,7 +10,11 @@ class MainApp extends StatelessWidget { @override Widget build(BuildContext context) { return const MaterialApp( - home: Scaffold(body: Center(child: Icon(TwoIcons.acUnit))), + home: Scaffold( + body: Center( + child: Column(children: [Icon(Icons.abc), Icon(TwoIcons.acUnit)]), + ), + ), ); } } diff --git a/dev/icon_treeshaker/example_flutter/pubspec.yaml b/dev/icon_treeshaker/example_flutter/pubspec.yaml index 4e4bc6db4d0ff..e81cadd989640 100644 --- a/dev/icon_treeshaker/example_flutter/pubspec.yaml +++ b/dev/icon_treeshaker/example_flutter/pubspec.yaml @@ -9,6 +9,9 @@ environment: dependencies: flutter: sdk: flutter + native_assets_cli: ^0.11.0 + icon_treeshaker: + path: ../ dev_dependencies: flutter_test: 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/lib/hook_helper.dart b/dev/icon_treeshaker/lib/hook_helper.dart index ead154c97b7e5..607bdb810659e 100644 --- a/dev/icon_treeshaker/lib/hook_helper.dart +++ b/dev/icon_treeshaker/lib/hook_helper.dart @@ -25,7 +25,7 @@ final class FontAsset { final int? weight; /// relative path in the package - final String file; + final Uri file; /// The package which contains this asset. final String package; @@ -43,7 +43,7 @@ final class FontAsset { style: jsonMap[_styleKey] as String?, weight: jsonMap[_weightKey] as int?, package: jsonMap[_packageKey] as String, - file: jsonMap[_fileKey] as String, + file: Uri.file(jsonMap[_fileKey] as String), ); } @@ -66,31 +66,31 @@ final class FontAsset { FontAsset.type, SplayTreeMap.from({ _packageKey: package, - _fileKey: file, + _fileKey: file.toFilePath(), _familyKey: family, - _styleKey: style, - _weightKey: weight, + if (style != null) _styleKey: style, + if (weight != null) _weightKey: weight, }), ); @override String toString() => 'FontAsset(${encode().encoding})'; - static const String type = 'icon'; + static const String type = 'font'; FontAsset({ - required this.family, - required this.style, - required this.weight, required this.file, required this.package, + required this.family, + this.style, + this.weight, }); FontAsset copyWith({ String? family, String? style, int? weight, - String? file, + Uri? file, String? package, }) => FontAsset( family: family ?? this.family, 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 d7af9d6c05192..36bc98bedad22 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 @@ -5,6 +5,7 @@ // 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'; @@ -43,6 +44,7 @@ final class DartHookResult { this.buildEnd, this.codeAssets, this.dataAssets, + this.fontAssets, this.dependencies, ); @@ -51,6 +53,7 @@ final class DartHookResult { buildEnd = DateTime.now(), codeAssets = const [], dataAssets = const [], + fontAssets = const [], dependencies = const []; factory DartHookResult.fromJson(Map json) { @@ -68,13 +71,18 @@ final class DartHookResult { for (final Object? json in json['data_assets'] as List? ?? const []) DataAsset.fromEncoded(EncodedAsset.fromJson(json! as Map)), ]; - return DartHookResult(buildStart, buildEnd, codeAssets, dataAssets, dependencies); + final List fontAssets = [ + for (final Object? json in json['data_assets'] as List? ?? const []) + FontAsset.fromEncoded(EncodedAsset.fromJson(json! as Map)), + ]; + return DartHookResult(buildStart, buildEnd, codeAssets, dataAssets, fontAssets, dependencies); } final DateTime buildStart; final DateTime buildEnd; final List codeAssets; final List dataAssets; + final List fontAssets; final List dependencies; Map toJson() => { @@ -83,6 +91,7 @@ final class DartHookResult { 'dependencies': [for (final Uri dep in dependencies) dep.toString()], 'code_assets': [for (final CodeAsset code in codeAssets) code.encode().toJson()], 'data_assets': [for (final DataAsset asset in dataAssets) asset.encode().toJson()], + 'font_assets': [for (final FontAsset asset in fontAssets) asset.encode().toJson()], }; /// The files that eventually should be bundled with the app. @@ -90,6 +99,7 @@ final class DartHookResult { for (final CodeAsset code in codeAssets) if (code.linkMode is DynamicLoadingBundled) code.file!, for (final DataAsset asset in dataAssets) asset.file, + for (final FontAsset asset in fontAssets) asset.file, ]; /// Whether caller may need to re-run the dart build. @@ -680,6 +690,7 @@ Future _runDartHooks({ buildAssetTypes: [ if (codeAssetSupport) CodeAsset.type, if (dataAssetSupport) DataAsset.type, + FontAsset.type, ], inputCreator: () { final BuildInputBuilder buildInputBuilder = BuildInputBuilder(); @@ -730,6 +741,7 @@ Future _runDartHooks({ buildAssetTypes: [ if (codeAssetSupport) CodeAsset.type, if (dataAssetSupport) DataAsset.type, + FontAsset.type, ], inputCreator: () { final LinkInputBuilder linkInputBuilder = LinkInputBuilder(); @@ -780,10 +792,23 @@ Future _runDartHooks({ .where((EncodedAsset asset) => asset.type == DataAsset.type) .map(DataAsset.fromEncoded) .toList(); + + final List fontAssets = + assets + .where((EncodedAsset asset) => asset.type == FontAsset.type) + .map(FontAsset.fromEncoded) + .toList(); globals.logger.printTrace('Building native assets for $targetString done.'); final DateTime buildEnd = DateTime.now(); - return DartHookResult(buildStart, buildEnd, codeAssets, dataAssets, dependencies.toList()); + return DartHookResult( + buildStart, + buildEnd, + codeAssets, + dataAssets, + fontAssets, + dependencies.toList(), + ); } List _architecturesForOS( From 02b187ec49de32580061d35d14ac2f8e15b3fd18 Mon Sep 17 00:00:00 2001 From: Moritz Date: Fri, 7 Mar 2025 15:57:13 +0100 Subject: [PATCH 23/25] More fixes --- dev/icon_treeshaker/hook/link.dart | 8 +++----- dev/icon_treeshaker/lib/src/icon_treeshaker_base.dart | 6 +----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/dev/icon_treeshaker/hook/link.dart b/dev/icon_treeshaker/hook/link.dart index 8122ac7739ed1..37ff27b3e8fc4 100644 --- a/dev/icon_treeshaker/hook/link.dart +++ b/dev/icon_treeshaker/hook/link.dart @@ -42,14 +42,13 @@ Future main(List arguments) async => await link(arguments, ( } final fontFile = fs.file(fontAsset.file); logger.info('Running $fontSubset on $fontFile.'); - var pathOfSubsetFont = p.join( - input.outputDirectoryShared.path, + var pathOfSubsetFont = input.outputDirectoryShared.resolve( //TODO(mosum): Handle duplicates - p.basename(fontAsset.file), + p.basename(fontAsset.file.path), ); final success = await subsetFont( fontFile: fontFile, - outputPath: pathOfSubsetFont, + outputPath: pathOfSubsetFont.toFilePath(), iconData: iconData, fs: fs, pathToSubsetBinary: fontSubset, @@ -109,7 +108,6 @@ Map getIconData( final List optionalCodePoints = isWeb ? [kSpacePoint] : []; map[font] = IconTreeShakerData( family: font.family, - relativePath: font.file, codePoints: instances.map((instance) => instance.codePoint).toList(), optionalCodePoints: optionalCodePoints, ); diff --git a/dev/icon_treeshaker/lib/src/icon_treeshaker_base.dart b/dev/icon_treeshaker/lib/src/icon_treeshaker_base.dart index c46a9a1b22d39..12091caf9a5b8 100644 --- a/dev/icon_treeshaker/lib/src/icon_treeshaker_base.dart +++ b/dev/icon_treeshaker/lib/src/icon_treeshaker_base.dart @@ -96,7 +96,6 @@ class IconTreeShakerData { /// All parameters are required. const IconTreeShakerData({ required this.family, - required this.relativePath, required this.codePoints, required this.optionalCodePoints, }); @@ -104,9 +103,6 @@ class IconTreeShakerData { /// The font family name, e.g. "MaterialIcons". final String family; - /// The relative path to the font file. - final String relativePath; - /// The list of code points for the font. final List codePoints; @@ -115,7 +111,7 @@ class IconTreeShakerData { final List optionalCodePoints; @override - String toString() => 'FontSubsetData($family, $relativePath, $codePoints)'; + String toString() => 'FontSubsetData($family, $codePoints)'; } class IconTreeShakerException implements Exception { From 09613675b0779b85fbb2d79bd1f5a196a0a9c79a Mon Sep 17 00:00:00 2001 From: Moritz Date: Fri, 7 Mar 2025 16:59:38 +0100 Subject: [PATCH 24/25] Add recordedUsages --- .../flutter_tools/lib/src/build_system/targets/common.dart | 4 +++- .../lib/src/build_system/targets/native_assets.dart | 6 ++++-- packages/flutter_tools/lib/src/compile.dart | 2 ++ .../lib/src/isolated/native_assets/native_assets.dart | 5 +++++ 4 files changed, 14 insertions(+), 3 deletions(-) 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 6324fb6f12c0a..77f0cdd4c38e1 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/common.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/common.dart @@ -159,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'; @@ -174,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 { @@ -288,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/native_assets.dart b/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart index 2620c5a652644..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 @@ -56,9 +56,11 @@ class DartBuild extends Target { runPackageName!, ); result = await runFlutterSpecificHooks( - environmentDefines: { + environmentDefines: { ...environment.defines, Artifact.fontSubset.name: environment.artifacts.getArtifactPath(Artifact.fontSubset), + KernelSnapshot.recordedUsagesName: + environment.buildDir.childFile(KernelSnapshot.recordedUsagesName).path, }, buildRunner: buildRunner, targetPlatform: targetPlatform, @@ -96,7 +98,7 @@ 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 diff --git a/packages/flutter_tools/lib/src/compile.dart b/packages/flutter_tools/lib/src/compile.dart index dbf60e73a6e4d..44325ff1a8eff 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-file=$recordedUsages', ]; _logger.printTrace(command.join(' ')); 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 36bc98bedad22..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 @@ -19,6 +19,7 @@ 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'; @@ -756,6 +757,10 @@ Future _runDartHooks({ macOS: macOSConfig, ); } + linkInputBuilder.setupLink( + assets: assets, + recordedUsesFile: Uri.file(environmentDefines[KernelSnapshot.recordedUsagesName]!), + ); return linkInputBuilder; }, inputValidator: From 7e7e966b09598a7413add168a2747c865cb6c064 Mon Sep 17 00:00:00 2001 From: Moritz Date: Mon, 10 Mar 2025 14:38:46 +0100 Subject: [PATCH 25/25] Use recorded-usages --- DEPS | 2 +- packages/flutter_tools/lib/src/compile.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/packages/flutter_tools/lib/src/compile.dart b/packages/flutter_tools/lib/src/compile.dart index 44325ff1a8eff..3acc744124f6d 100644 --- a/packages/flutter_tools/lib/src/compile.dart +++ b/packages/flutter_tools/lib/src/compile.dart @@ -370,7 +370,7 @@ class KernelCompiler { '--verbosity=error', ...?extraFrontEndOptions, if (mainUri != null) mainUri else '--native-assets-only', - if (recordedUsages != null) '--recorded-usages-file=$recordedUsages', + if (recordedUsages != null) '--recorded-usages=$recordedUsages', ]; _logger.printTrace(command.join(' '));