From 09686a04c952d62fa739afecd29d34e24dbacfeb Mon Sep 17 00:00:00 2001 From: Christopher Fujino Date: Sat, 7 May 2022 11:49:07 -0700 Subject: [PATCH] [flutter_tools] add --uninstall-first flag and pipe it through to ios-deploy (#102948) --- dev/devicelab/lib/tasks/hot_mode_tests.dart | 10 ++++- .../flutter_tools/lib/src/commands/run.dart | 8 ++++ packages/flutter_tools/lib/src/device.dart | 9 +++++ .../flutter_tools/lib/src/ios/devices.dart | 2 + .../flutter_tools/lib/src/ios/ios_deploy.dart | 6 +++ .../commands.shard/hermetic/run_test.dart | 38 ++++++++++++++++++- .../general.shard/ios/ios_deploy_test.dart | 2 + 7 files changed, 73 insertions(+), 2 deletions(-) diff --git a/dev/devicelab/lib/tasks/hot_mode_tests.dart b/dev/devicelab/lib/tasks/hot_mode_tests.dart index 4618a4c0f6..65c2f8e324 100644 --- a/dev/devicelab/lib/tasks/hot_mode_tests.dart +++ b/dev/devicelab/lib/tasks/hot_mode_tests.dart @@ -33,7 +33,15 @@ TaskFunction createHotModeTest({String? deviceIdOverride, Map? e final File benchmarkFile = file(path.join(_editedFlutterGalleryDir.path, 'hot_benchmark.json')); rm(benchmarkFile); final List options = [ - '--hot', '-d', deviceIdOverride!, '--benchmark', '--resident', '--no-android-gradle-daemon', '--no-publish-port', '--verbose', + '--hot', + '-d', + deviceIdOverride!, + '--benchmark', + '--resident', + '--no-android-gradle-daemon', + '--no-publish-port', + '--verbose', + '--uninstall-first', ]; int hotReloadCount = 0; late Map smallReloadData; diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart index 0f76f50129..e9c10531a1 100644 --- a/packages/flutter_tools/lib/src/commands/run.dart +++ b/packages/flutter_tools/lib/src/commands/run.dart @@ -140,6 +140,11 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment 'this option multiple times each with one argument to pass ' 'multiple arguments to the Dart entrypoint. Currently this is ' 'only supported on desktop platforms.', + ) + ..addFlag('uninstall-first', + hide: !verboseHelp, + help: 'Uninstall previous versions of the app on the device ' + 'before reinstalling. Currently only supported on iOS.', ); usesWebOptions(verboseHelp: verboseHelp); usesTargetOption(); @@ -166,6 +171,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment bool get runningWithPrebuiltApplication => argResults[FlutterOptions.kUseApplicationBinary] != null; bool get trackWidgetCreation => boolArg('track-widget-creation'); bool get enableImpeller => boolArg('enable-impeller'); + bool get uninstallFirst => boolArg('uninstall-first'); @override bool get reportNullSafety => true; @@ -196,6 +202,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment webRunHeadless: featureFlags.isWebEnabled && boolArg('web-run-headless'), webBrowserDebugPort: browserDebugPort, enableImpeller: enableImpeller, + uninstallFirst: uninstallFirst, ); } else { return DebuggingOptions.enabled( @@ -240,6 +247,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment nullAssertions: boolArg('null-assertions'), nativeNullAssertions: boolArg('native-null-assertions'), enableImpeller: enableImpeller, + uninstallFirst: uninstallFirst, ); } } diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart index 8698acf083..845c1d32da 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart @@ -792,6 +792,7 @@ class DebuggingOptions { this.nullAssertions = false, this.nativeNullAssertions = false, this.enableImpeller = false, + this.uninstallFirst = false, }) : debuggingEnabled = true; DebuggingOptions.disabled(this.buildInfo, { @@ -808,6 +809,7 @@ class DebuggingOptions { this.cacheSkSL = false, this.traceAllowlist, this.enableImpeller = false, + this.uninstallFirst = false, }) : debuggingEnabled = false, useTestFonts = false, startPaused = false, @@ -876,6 +878,7 @@ class DebuggingOptions { required this.nullAssertions, required this.nativeNullAssertions, required this.enableImpeller, + required this.uninstallFirst, }); final bool debuggingEnabled; @@ -912,6 +915,11 @@ class DebuggingOptions { final bool webUseSseForInjectedClient; final bool enableImpeller; + /// Whether the tool should try to uninstall a previously installed version of the app. + /// + /// This is not implemented for every platform. + final bool uninstallFirst; + /// Whether to run the browser in headless mode. /// /// Some CI environments do not provide a display and fail to launch the @@ -1026,6 +1034,7 @@ class DebuggingOptions { nullAssertions: (json['nullAssertions'] as bool?)!, nativeNullAssertions: (json['nativeNullAssertions'] as bool?)!, enableImpeller: (json['enableImpeller'] as bool?) ?? false, + uninstallFirst: (json['uninstallFirst'] as bool?) ?? false, ); } diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart index a8c88864ef..53deabdf5f 100644 --- a/packages/flutter_tools/lib/src/ios/devices.dart +++ b/packages/flutter_tools/lib/src/ios/devices.dart @@ -394,6 +394,7 @@ class IOSDevice extends Device { appDeltaDirectory: package.appDeltaDirectory, launchArguments: launchArguments, interfaceType: interfaceType, + uninstallFirst: debuggingOptions.uninstallFirst, ); if (deviceLogReader is IOSDeviceLogReader) { deviceLogReader.debuggerStream = iosDeployDebugger; @@ -415,6 +416,7 @@ class IOSDevice extends Device { appDeltaDirectory: package.appDeltaDirectory, launchArguments: launchArguments, interfaceType: interfaceType, + uninstallFirst: debuggingOptions.uninstallFirst, ); } else { installationResult = await iosDeployDebugger!.launchAndAttach() ? 0 : 1; diff --git a/packages/flutter_tools/lib/src/ios/ios_deploy.dart b/packages/flutter_tools/lib/src/ios/ios_deploy.dart index e684750ece..e6d2bb7eca 100644 --- a/packages/flutter_tools/lib/src/ios/ios_deploy.dart +++ b/packages/flutter_tools/lib/src/ios/ios_deploy.dart @@ -127,6 +127,7 @@ class IOSDeploy { required List launchArguments, required IOSDeviceConnectionInterface interfaceType, Directory? appDeltaDirectory, + required bool uninstallFirst, }) { appDeltaDirectory?.createSync(recursive: true); // Interactive debug session to support sending the lldb detach command. @@ -144,6 +145,8 @@ class IOSDeploy { '--app_deltas', appDeltaDirectory.path, ], + if (uninstallFirst) + '--uninstall', '--debug', if (interfaceType != IOSDeviceConnectionInterface.network) '--no-wifi', @@ -168,6 +171,7 @@ class IOSDeploy { required String bundlePath, required List launchArguments, required IOSDeviceConnectionInterface interfaceType, + required bool uninstallFirst, Directory? appDeltaDirectory, }) async { appDeltaDirectory?.createSync(recursive: true); @@ -183,6 +187,8 @@ class IOSDeploy { ], if (interfaceType != IOSDeviceConnectionInterface.network) '--no-wifi', + if (uninstallFirst) + '--uninstall', '--justlaunch', if (launchArguments.isNotEmpty) ...[ '--args', 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 9d6f843781..1a65c4928c 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart @@ -369,6 +369,42 @@ void main() { Cache: () => Cache.test(processManager: FakeProcessManager.any()), }); + testUsingContext('forwards --uninstall-only to DebuggingOptions', () async { + final RunCommand command = RunCommand(); + final FakeDevice mockDevice = FakeDevice( + sdkNameAndVersion: 'iOS 13', + )..startAppSuccess = false; + + mockDeviceManager + ..devices = [ + mockDevice, + ] + ..targetDevices = [ + mockDevice, + ]; + + // Causes swift to be detected in the analytics. + fs.currentDirectory.childDirectory('ios').childFile('AppDelegate.swift').createSync(recursive: true); + + await expectToolExitLater(createTestCommandRunner(command).run([ + 'run', + '--no-pub', + '--no-hot', + '--uninstall-first', + ]), isNull); + + final DebuggingOptions options = await command.createDebuggingOptions(false); + expect(options.uninstallFirst, isTrue); + }, overrides: { + Artifacts: () => artifacts, + Cache: () => Cache.test(processManager: FakeProcessManager.any()), + DeviceManager: () => mockDeviceManager, + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + Usage: () => usage, + }); + + testUsingContext('passes device target platform to usage', () async { final RunCommand command = RunCommand(); final FakeDevice mockDevice = FakeDevice(sdkNameAndVersion: 'iOS 13') @@ -407,7 +443,7 @@ void main() { Usage: () => usage, }); - group('--machine', () { + group('--machine', () { testUsingContext('enables multidex by default', () async { final DaemonCapturingRunCommand command = DaemonCapturingRunCommand(); final FakeDevice device = FakeDevice(); diff --git a/packages/flutter_tools/test/general.shard/ios/ios_deploy_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_deploy_test.dart index 76dce321cc..8fd3a5d0ae 100644 --- a/packages/flutter_tools/test/general.shard/ios/ios_deploy_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/ios_deploy_test.dart @@ -53,6 +53,7 @@ void main () { '/', '--app_deltas', 'app-delta', + '--uninstall', '--debug', '--args', [ @@ -73,6 +74,7 @@ void main () { appDeltaDirectory: appDeltaDirectory, launchArguments: ['--enable-dart-profiling'], interfaceType: IOSDeviceConnectionInterface.network, + uninstallFirst: true, ); expect(iosDeployDebugger.logLines, emits('Did finish launching.'));