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..1cc79f73fe24b 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. @@ -153,7 +159,7 @@ class KernelSnapshot extends Target { @override List get outputs => const [ Source.pattern('{BUILD_DIR}/${KernelSnapshot.dillName}'), - // TODO(mosuem): Should output resources.json. https://github.com/flutter/flutter/issues/146263 + Source.pattern('{BUILD_DIR}/$recordedUsagesFileName'), ]; static const String depfile = 'kernel_snapshot_program.d'; @@ -168,6 +174,7 @@ class KernelSnapshot extends Target { ]; static const String dillName = 'app.dill'; + static const String recordedUsagesFileName = 'recorded_usages.json'; @override Future build(Environment environment) async { @@ -254,6 +261,8 @@ class KernelSnapshot extends Target { final String dillPath = environment.buildDir.childFile(dillName).path; + final String recordedUsagesFile = environment.buildDir.childFile(recordedUsagesFileName).path; + final List dartDefines = decodeDartDefines(environment.defines, kDartDefines); await _addFlavorToDartDefines(dartDefines, environment, targetPlatform); @@ -282,6 +291,7 @@ class KernelSnapshot extends Target { buildDir: environment.buildDir, targetOS: targetOS, checkDartPluginRegistry: environment.generateDartPluginRegistry, + recordedUsagesFile: recordedUsagesFile, ); if (output == null || output.errorCount != 0) { throw Exception(); diff --git a/packages/flutter_tools/lib/src/build_system/targets/ios.dart b/packages/flutter_tools/lib/src/build_system/targets/ios.dart index 37331f279a8d8..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..a9c68d836c653 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,64 @@ import '../../dart/package_map.dart'; import '../../isolated/native_assets/native_assets.dart'; import '../build_system.dart'; import '../depfile.dart'; -import '../exceptions.dart'; +import '../exceptions.dart' show MissingDefineException; import 'common.dart'; +export '../../isolated/native_assets/native_assets.dart' + show CodeAsset, DartBuildResult, DataAsset, DynamicLoadingBundled; + /// Runs the dart build of the app. abstract class DartBuild extends Target { - const DartBuild({@visibleForTesting FlutterNativeAssetsBuildRunner? buildRunner}) - : _buildRunner = buildRunner; + const DartBuild({ + @visibleForTesting FlutterNativeAssetsBuildRunner? buildRunner, + required this.isWeb, + }) : _buildRunner = buildRunner; final FlutterNativeAssetsBuildRunner? _buildRunner; + // The target platform is not set on the web, so we store it explicitly. + final bool isWeb; + @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 = + isWeb + ? TargetPlatform.web_javascript + : _getTargetPlatformFromEnvironment(environment, name); + + final PackageConfig packageConfig = await loadPackageConfigWithLogging( + fileSystem.file(environment.packageConfigPath), + logger: environment.logger, + ); + final Uri projectUri = environment.projectDir.uri; + final File recordedUsagesFileCandidate = environment.buildDir.childFile( + KernelSnapshot.recordedUsagesFileName, + ); + final Uri? recordedUsagesFile = + recordedUsagesFileCandidate.existsSync() ? recordedUsagesFileCandidate.uri : null; + + 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, + recordedUsagesFile: recordedUsagesFile, + ); final File dartBuildResultJsonFile = environment.buildDir.childFile(dartBuildResultFilename); if (!dartBuildResultJsonFile.parent.existsSync()) { @@ -89,7 +102,7 @@ abstract class DartBuild extends Target { ), // If different packages are resolved, different native assets might need to be built. Source.pattern('{WORKSPACE_DIR}/.dart_tool/package_config_subset'), - // TODO(mosuem): Should consume resources.json. https://github.com/flutter/flutter/issues/146263 + Source.pattern('{BUILD_DIR}/${KernelSnapshot.recordedUsagesFileName}'), ]; @override @@ -106,6 +119,9 @@ 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, ); @@ -116,12 +132,19 @@ abstract class DartBuild extends Target { } class DartBuildForNative extends DartBuild { - const DartBuildForNative({@visibleForTesting super.buildRunner}); + const DartBuildForNative({@visibleForTesting super.buildRunner}) : super(isWeb: false); @override List get dependencies => const [KernelSnapshot()]; } +class DartBuildForDataAssets extends DartBuild { + const DartBuildForDataAssets({@visibleForTesting super.buildRunner, required super.isWeb}); + + @override + List get dependencies => []; +} + /// Installs the code assets from a [DartBuild] Flutter app. /// /// The build mode and target architecture can be changed from the 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..f1f43f8e6ed2e 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 DartBuildForDataAssets(isWeb: true), + ]; 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/compile.dart b/packages/flutter_tools/lib/src/compile.dart index dbf60e73a6e4d..8a22a887252ff 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? recordedUsagesFile, }) async { final TargetPlatform? platform = targetModel == TargetModel.dartdevc ? TargetPlatform.web_javascript : null; @@ -365,6 +366,7 @@ class KernelCompiler { '-Dflutter.dart_plugin_registrant=$dartPluginRegistrantUri', ], if (nativeAssets != null) ...['--native-assets', nativeAssets], + if (recordedUsagesFile != null) ...['--recorded-usages-file', recordedUsagesFile], // See: https://github.com/flutter/flutter/issues/103994 '--verbosity=error', ...?extraFrontEndOptions, 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..c9c815bbca5df 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 @@ -71,9 +124,17 @@ Future runFlutterSpecificDartBuild({ required TargetPlatform targetPlatform, required Uri projectUri, required FileSystem fileSystem, + required Uri? recordedUsagesFile, }) async { - final OS targetOS = getNativeOSFromTargetPlatform(targetPlatform); + final bool isWeb = targetPlatform == TargetPlatform.web_javascript; + final OS? targetOS = getNativeOSFromTargetPlatform(targetPlatform); + assert(isWeb || 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,25 +146,30 @@ 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 + ? 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), fileSystem: fileSystem, targetOS: targetOS, + recordedUsagesFile: recordedUsagesFile, ); return result; } @@ -116,14 +182,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, @@ -172,6 +240,7 @@ abstract interface class FlutterNativeAssetsBuildRunner { required ApplicationAssetValidator applicationAssetValidator, required Uri workingDirectory, required BuildResult buildResult, + required Uri? recordedUsagesFile, }); /// The C compiler config to use for compilation. @@ -273,6 +342,7 @@ class FlutterNativeAssetsBuildRunnerImpl implements FlutterNativeAssetsBuildRunn required ApplicationAssetValidator applicationAssetValidator, required Uri workingDirectory, required BuildResult buildResult, + required Uri? recordedUsagesFile, }) { return _buildRunner.link( buildAssetTypes: buildAssetTypes, @@ -281,6 +351,7 @@ class FlutterNativeAssetsBuildRunnerImpl implements FlutterNativeAssetsBuildRunn linkValidator: linkValidator, applicationAssetValidator: applicationAssetValidator, buildResult: buildResult, + resourceIdentifiers: recordedUsagesFile, ); } @@ -371,11 +442,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 +480,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 +627,24 @@ 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, + required Uri? recordedUsagesFile, }) 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 +677,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 && codeAssetSupport) { + 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 +722,54 @@ 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 && codeAssetSupport) { + 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, + recordedUsagesFile: recordedUsagesFile, + ); + if (linkResult == null) { + _throwNativeAssetsLinkFailed(); } + assets.addAll(linkResult.encodedAssets); + dependencies.addAll([ + ...linkResult.dependencies, + if (recordedUsagesFile != null) recordedUsagesFile, + ]); } final List codeAssets = @@ -674,8 +777,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 +873,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 +906,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..1fe675dd2840a 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'); @@ -68,6 +68,7 @@ Future testCompilerBuildNativeAssets(BuildInfo buildInfo) async { targetPlatform: TargetPlatform.tester, projectUri: projectUri, fileSystem: globals.fs, + recordedUsagesFile: null, ); // Then "install" the code assets so they can be used at runtime. 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 a3a32e79c83b5..590665681920d 100644 --- a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart +++ b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart @@ -644,6 +644,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(isWeb: true), 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..90ecaca1225ff 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 bool isWeb}) async { + globals.printTrace('runDartBuild()'); + 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( + const DartBuildForDataAssets(isWeb: true), + _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..bd29fa871af55 100644 --- a/packages/flutter_tools/lib/src/run_hot.dart +++ b/packages/flutter_tools/lib/src/run_hot.dart @@ -495,6 +495,7 @@ class HotRunner extends ResidentRunner { if (rebuildBundle) { globals.printTrace('Updating assets'); final int result = await assetBundle.build( + dartBuildResult: await runDartBuild(isWeb: false), packageConfigPath: debuggingOptions.buildInfo.packageConfigPath, flavor: debuggingOptions.buildInfo.flavor, ); 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/android/native_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/android/native_assets_test.dart index 3d188e9233867..a5749ed865684 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 @@ -95,6 +95,7 @@ void main() { projectUri: projectUri, fileSystem: fileSystem, buildRunner: buildRunner, + recordedUsagesFile: null, ); await installCodeAssets( dartBuildResult: result, @@ -139,6 +140,7 @@ void main() { projectUri: projectUri, fileSystem: fileSystem, buildRunner: _BuildRunnerWithoutNdk(), + recordedUsagesFile: null, ); expect( (globals.logger as BufferLogger).traceText, @@ -164,6 +166,7 @@ void main() { projectUri: projectUri, fileSystem: fileSystem, buildRunner: _BuildRunnerWithoutNdk(packagesWithNativeAssetsResult: ['bar']), + recordedUsagesFile: null, ), throwsToolExit(message: 'Android NDK Clang could not be found.'), ); 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 9812e9b284c88..e1e362b4b8f65 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,14 +64,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 { await createPackageConfig(iosEnvironment); diff --git a/packages/flutter_tools/test/general.shard/isolated/fake_native_assets_build_runner.dart b/packages/flutter_tools/test/general.shard/isolated/fake_native_assets_build_runner.dart index 5bc0f27ec8c67..8d133da218bb7 100644 --- a/packages/flutter_tools/test/general.shard/isolated/fake_native_assets_build_runner.dart +++ b/packages/flutter_tools/test/general.shard/isolated/fake_native_assets_build_runner.dart @@ -76,6 +76,7 @@ class FakeFlutterNativeAssetsBuildRunner implements FlutterNativeAssetsBuildRunn required ApplicationAssetValidator applicationAssetValidator, required Uri workingDirectory, required BuildResult buildResult, + required Uri? recordedUsagesFile, }) async { LinkResult? result = linkResult; for (final String package in packagesWithNativeAssetsResult) { 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 ed54ae8d360a5..e9a31652d35e2 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 @@ -66,6 +66,7 @@ void main() { projectUri: projectUri, fileSystem: fileSystem, buildRunner: _BuildRunnerWithoutClang(), + recordedUsagesFile: null, ); expect( (globals.logger as BufferLogger).traceText, 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 55d0b74268850..a10450d85460b 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 @@ -305,6 +305,7 @@ void main() { projectUri: projectUri, fileSystem: fileSystem, buildRunner: buildRunner, + recordedUsagesFile: null, ); final Uri nativeAssetsFileUri = flutterTester 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..1bf8a7c5f8647 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 @@ -82,6 +82,7 @@ void main() { targetPlatform: TargetPlatform.linux_x64, projectUri: projectUri, fileSystem: fileSystem, + recordedUsagesFile: null, buildRunner: FakeFlutterNativeAssetsBuildRunner( packagesWithNativeAssetsResult: ['bar'], buildResult: FakeFlutterNativeAssetsBuilderResult.fromAssets(codeAssets: codeAssets), @@ -113,15 +114,12 @@ void main() { targetPlatform: TargetPlatform.windows_x64, projectUri: projectUri, fileSystem: fileSystem, + recordedUsagesFile: null, buildRunner: FakeFlutterNativeAssetsBuildRunner( 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`'), ); }, ); @@ -147,6 +145,7 @@ void main() { targetPlatform: TargetPlatform.windows_x64, projectUri: projectUri, fileSystem: fileSystem, + recordedUsagesFile: null, buildRunner: FakeFlutterNativeAssetsBuildRunner( packagesWithNativeAssetsResult: ['bar'], ), @@ -189,6 +188,7 @@ void main() { targetPlatform: TargetPlatform.linux_x64, projectUri: projectUri, fileSystem: fileSystem, + recordedUsagesFile: null, buildRunner: FakeFlutterNativeAssetsBuildRunner( packagesWithNativeAssetsResult: ['bar'], buildResult: null, @@ -234,6 +234,7 @@ void main() { targetPlatform: TargetPlatform.linux_x64, projectUri: projectUri, fileSystem: fileSystem, + recordedUsagesFile: null, buildRunner: FakeFlutterNativeAssetsBuildRunner( packagesWithNativeAssetsResult: ['bar'], buildResult: FakeFlutterNativeAssetsBuilderResult.fromAssets( 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 85e6b5df2fa8d..88d55813bdd56 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 @@ -115,6 +115,7 @@ void main() { targetPlatform: targetPlatform, projectUri: projectUri, fileSystem: fileSystem, + recordedUsagesFile: null, buildRunner: buildRunner, ); final String expectedDirectory = 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;