diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart index 49410f06bc1..acb11a0a456 100644 --- a/packages/flutter_tools/lib/src/commands/run.dart +++ b/packages/flutter_tools/lib/src/commands/run.dart @@ -140,7 +140,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment 'startup. By default this is main(List args). Specify ' 'this option multiple times each with one argument to pass ' 'multiple arguments to the Dart entrypoint. Currently this is ' - 'only supported on desktop platforms.' + 'only supported on desktop platforms.', ); usesWebOptions(verboseHelp: verboseHelp); usesTargetOption(); @@ -155,6 +155,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment addDevToolsOptions(verboseHelp: verboseHelp); addAndroidSpecificBuildOptions(hide: !verboseHelp); usesFatalWarningsOption(verboseHelp: verboseHelp); + addEnableImpellerFlag(verboseHelp: verboseHelp); } bool get traceStartup => boolArg('trace-startup'); @@ -164,6 +165,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment bool get disableServiceAuthCodes => boolArg('disable-service-auth-codes'); bool get runningWithPrebuiltApplication => argResults['use-application-binary'] != null; bool get trackWidgetCreation => boolArg('track-widget-creation'); + bool get enableImpeller => boolArg('enable-impeller'); @override bool get reportNullSafety => true; @@ -193,6 +195,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment webEnableExposeUrl: featureFlags.isWebEnabled && boolArg('web-allow-expose-url'), webRunHeadless: featureFlags.isWebEnabled && boolArg('web-run-headless'), webBrowserDebugPort: browserDebugPort, + enableImpeller: enableImpeller, ); } else { return DebuggingOptions.enabled( @@ -235,6 +238,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment && !runningWithPrebuiltApplication, nullAssertions: boolArg('null-assertions'), nativeNullAssertions: boolArg('native-null-assertions'), + enableImpeller: enableImpeller, ); } } @@ -438,6 +442,7 @@ class RunCommand extends RunCommandBase { commandRunProjectModule: FlutterProject.current().isModule, commandRunProjectHostLanguage: hostLanguage.join(','), commandRunAndroidEmbeddingVersion: androidEmbeddingVersion, + commandRunEnableImpeller: enableImpeller, ); } diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart index 56d9a15d571..0a2dcba85be 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart @@ -790,6 +790,7 @@ class DebuggingOptions { this.fastStart = false, this.nullAssertions = false, this.nativeNullAssertions = false, + this.enableImpeller = false, }) : debuggingEnabled = true; DebuggingOptions.disabled(this.buildInfo, { @@ -805,6 +806,7 @@ class DebuggingOptions { this.webLaunchUrl, this.cacheSkSL = false, this.traceAllowlist, + this.enableImpeller = false, }) : debuggingEnabled = false, useTestFonts = false, startPaused = false, @@ -870,6 +872,7 @@ class DebuggingOptions { required this.fastStart, required this.nullAssertions, required this.nativeNullAssertions, + required this.enableImpeller, }); final bool debuggingEnabled; @@ -903,6 +906,7 @@ class DebuggingOptions { final bool webUseSseForDebugProxy; final bool webUseSseForDebugBackend; final bool webUseSseForInjectedClient; + final bool enableImpeller; /// Whether to run the browser in headless mode. /// @@ -972,6 +976,7 @@ class DebuggingOptions { 'fastStart': fastStart, 'nullAssertions': nullAssertions, 'nativeNullAssertions': nativeNullAssertions, + 'enableImpeller': enableImpeller, }; static DebuggingOptions fromJson(Map json, BuildInfo buildInfo) => @@ -1014,6 +1019,7 @@ class DebuggingOptions { fastStart: (json['fastStart'] as bool?)!, nullAssertions: (json['nullAssertions'] as bool?)!, nativeNullAssertions: (json['nativeNullAssertions'] as bool?)!, + enableImpeller: (json['enableImpeller'] as bool?) ?? false, ); } diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart index 133d395d048..f51d73789ed 100644 --- a/packages/flutter_tools/lib/src/ios/devices.dart +++ b/packages/flutter_tools/lib/src/ios/devices.dart @@ -373,6 +373,7 @@ class IOSDevice extends Device { if (debuggingOptions.purgePersistentCache) '--purge-persistent-cache', if (route != null) '--route=$route', if (platformArgs['trace-startup'] as bool? ?? false) '--trace-startup', + if (debuggingOptions.enableImpeller) '--enable-impeller', ]; final Status installStatus = _logger.startProgress( diff --git a/packages/flutter_tools/lib/src/reporting/custom_dimensions.dart b/packages/flutter_tools/lib/src/reporting/custom_dimensions.dart index 6fbd116f274..b9cbeea8193 100644 --- a/packages/flutter_tools/lib/src/reporting/custom_dimensions.dart +++ b/packages/flutter_tools/lib/src/reporting/custom_dimensions.dart @@ -66,6 +66,7 @@ class CustomDimensions { this.hotEventScannedSourcesCount, this.hotEventReassembleTimeInMs, this.hotEventReloadVMTimeInMs, + this.commandRunEnableImpeller, }); final String? sessionHostOsDetails; // cd1 @@ -123,6 +124,7 @@ class CustomDimensions { final int? hotEventScannedSourcesCount; // cd 53 final int? hotEventReassembleTimeInMs; // cd 54 final int? hotEventReloadVMTimeInMs; // cd 55 + final bool? commandRunEnableImpeller; // cd 56 /// Convert to a map that will be used to upload to the analytics backend. Map toMap() => { @@ -181,6 +183,7 @@ class CustomDimensions { if (hotEventScannedSourcesCount != null) cdKey(CustomDimensionsEnum.hotEventScannedSourcesCount): hotEventScannedSourcesCount.toString(), if (hotEventReassembleTimeInMs != null) cdKey(CustomDimensionsEnum.hotEventReassembleTimeInMs): hotEventReassembleTimeInMs.toString(), if (hotEventReloadVMTimeInMs != null) cdKey(CustomDimensionsEnum.hotEventReloadVMTimeInMs): hotEventReloadVMTimeInMs.toString(), + if (commandRunEnableImpeller != null) cdKey(CustomDimensionsEnum.commandRunEnableImpeller): commandRunEnableImpeller.toString(), }; /// Merge the values of two [CustomDimensions] into one. If a value is defined @@ -246,6 +249,7 @@ class CustomDimensions { hotEventScannedSourcesCount: other.hotEventScannedSourcesCount ?? hotEventScannedSourcesCount, hotEventReassembleTimeInMs: other.hotEventReassembleTimeInMs ?? hotEventReassembleTimeInMs, hotEventReloadVMTimeInMs: other.hotEventReloadVMTimeInMs ?? hotEventReloadVMTimeInMs, + commandRunEnableImpeller: other.commandRunEnableImpeller ?? commandRunEnableImpeller, ); } @@ -305,6 +309,7 @@ class CustomDimensions { hotEventScannedSourcesCount: _extractInt(map, CustomDimensionsEnum.hotEventScannedSourcesCount), hotEventReassembleTimeInMs: _extractInt(map, CustomDimensionsEnum.hotEventReassembleTimeInMs), hotEventReloadVMTimeInMs: _extractInt(map, CustomDimensionsEnum.hotEventReloadVMTimeInMs), + commandRunEnableImpeller: _extractBool(map, CustomDimensionsEnum.commandRunEnableImpeller), ); static bool? _extractBool(Map map, CustomDimensionsEnum field) => @@ -390,6 +395,7 @@ enum CustomDimensionsEnum { hotEventScannedSourcesCount, // cd53 hotEventReassembleTimeInMs, // cd54 hotEventReloadVMTimeInMs, // cd55 + commandRunEnableImpeller, // cd56 } String cdKey(CustomDimensionsEnum cd) => 'cd${cd.index + 1}'; diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index c6066cf7cd0..6f11248bc82 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart @@ -948,6 +948,16 @@ abstract class FlutterCommand extends Command { ); } + void addEnableImpellerFlag({required bool verboseHelp}) { + argParser.addFlag('enable-impeller', + negatable: false, + hide: !verboseHelp, + help: 'Whether to enable the experimental Impeller rendering engine. ' + 'Impeller is currently only supported on iOS. This flag will ' + 'be ignored when targeting other platforms.', + ); + } + /// Compute the [BuildInfo] for the current flutter command. /// Commands that build multiple build modes can pass in a [forcedBuildMode] /// to be used instead of parsing flags. diff --git a/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart index b901cf05af2..c91169cd45e 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart @@ -207,6 +207,26 @@ void main() { ProcessManager: () => FakeProcessManager.any(), Pub: () => FakePub(), }); + + testUsingContext('--enable-impeller flag propagates to debugging options', () async { + final DriveCommand command = DriveCommand(fileSystem: fileSystem, logger: logger, platform: platform); + fileSystem.file('lib/main.dart').createSync(recursive: true); + fileSystem.file('test_driver/main_test.dart').createSync(recursive: true); + fileSystem.file('pubspec.yaml').createSync(); + + await expectLater(() => createTestCommandRunner(command).run([ + 'drive', + '--enable-impeller', + ]), throwsToolExit()); + + final DebuggingOptions options = await command.createDebuggingOptions(false); + + expect(options.enableImpeller, true); + }, overrides: { + Cache: () => Cache.test(processManager: FakeProcessManager.any()), + FileSystem: () => MemoryFileSystem.test(), + ProcessManager: () => FakeProcessManager.any(), + }); } // Unfortunately Device, despite not being immutable, has an `operator ==`. diff --git a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart index 40455d65a50..a676860b504 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart @@ -394,6 +394,7 @@ void main() { TestUsageCommand('run', parameters: CustomDimensions.fromMap({ 'cd3': 'false', 'cd4': 'ios', 'cd22': 'iOS 13', 'cd23': 'debug', 'cd18': 'false', 'cd15': 'swift', 'cd31': 'false', + 'cd56': 'false', }) ))); }, overrides: { @@ -622,6 +623,22 @@ void main() { ProcessManager: () => FakeProcessManager.any(), }); + testUsingContext('--enable-impeller flag propagates to debugging options', () async { + final RunCommand command = RunCommand(); + await expectLater(() => createTestCommandRunner(command).run([ + 'run', + '--enable-impeller', + ]), throwsToolExit()); + + final DebuggingOptions options = await command.createDebuggingOptions(false); + + expect(options.enableImpeller, true); + }, overrides: { + Cache: () => Cache.test(processManager: FakeProcessManager.any()), + FileSystem: () => MemoryFileSystem.test(), + ProcessManager: () => FakeProcessManager.any(), + }); + testUsingContext('fails when "--web-launch-url" is not supported', () async { final RunCommand command = RunCommand(); await expectLater( diff --git a/packages/flutter_tools/test/general.shard/device_test.dart b/packages/flutter_tools/test/general.shard/device_test.dart index b2c9d94b2a8..918dd090a85 100644 --- a/packages/flutter_tools/test/general.shard/device_test.dart +++ b/packages/flutter_tools/test/general.shard/device_test.dart @@ -521,6 +521,7 @@ void main() { dartEntrypointArgs: ['a', 'b'], dartFlags: 'c', deviceVmServicePort: 1234, + enableImpeller: true, ); final String jsonString = json.encode(original.toJson()); final Map decoded = castStringKeyedMap(json.decode(jsonString))!; @@ -531,6 +532,7 @@ void main() { expect(deserialized.dartEntrypointArgs, original.dartEntrypointArgs); expect(deserialized.dartFlags, original.dartFlags); expect(deserialized.deviceVmServicePort, original.deviceVmServicePort); + expect(deserialized.enableImpeller, original.enableImpeller); }); }); } diff --git a/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart index 8145cf8fb19..270e26c598a 100644 --- a/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart @@ -285,6 +285,7 @@ void main() { '--verbose-logging', '--cache-sksl', '--purge-persistent-cache', + '--enable-impeller', ].join(' '), ], environment: const { 'PATH': '/usr/bin:null', @@ -332,6 +333,7 @@ void main() { purgePersistentCache: true, verboseSystemLogs: true, nullAssertions: true, + enableImpeller: true, ), platformArgs: {}, );