Skip to content

Commit 09686a0

Browse files
[flutter_tools] add --uninstall-first flag and pipe it through to ios-deploy (#102948)
1 parent 4bed767 commit 09686a0

File tree

7 files changed

+73
-2
lines changed

7 files changed

+73
-2
lines changed

dev/devicelab/lib/tasks/hot_mode_tests.dart

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,15 @@ TaskFunction createHotModeTest({String? deviceIdOverride, Map<String, String>? e
3333
final File benchmarkFile = file(path.join(_editedFlutterGalleryDir.path, 'hot_benchmark.json'));
3434
rm(benchmarkFile);
3535
final List<String> options = <String>[
36-
'--hot', '-d', deviceIdOverride!, '--benchmark', '--resident', '--no-android-gradle-daemon', '--no-publish-port', '--verbose',
36+
'--hot',
37+
'-d',
38+
deviceIdOverride!,
39+
'--benchmark',
40+
'--resident',
41+
'--no-android-gradle-daemon',
42+
'--no-publish-port',
43+
'--verbose',
44+
'--uninstall-first',
3745
];
3846
int hotReloadCount = 0;
3947
late Map<String, dynamic> smallReloadData;

packages/flutter_tools/lib/src/commands/run.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,11 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
140140
'this option multiple times each with one argument to pass '
141141
'multiple arguments to the Dart entrypoint. Currently this is '
142142
'only supported on desktop platforms.',
143+
)
144+
..addFlag('uninstall-first',
145+
hide: !verboseHelp,
146+
help: 'Uninstall previous versions of the app on the device '
147+
'before reinstalling. Currently only supported on iOS.',
143148
);
144149
usesWebOptions(verboseHelp: verboseHelp);
145150
usesTargetOption();
@@ -166,6 +171,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
166171
bool get runningWithPrebuiltApplication => argResults[FlutterOptions.kUseApplicationBinary] != null;
167172
bool get trackWidgetCreation => boolArg('track-widget-creation');
168173
bool get enableImpeller => boolArg('enable-impeller');
174+
bool get uninstallFirst => boolArg('uninstall-first');
169175

170176
@override
171177
bool get reportNullSafety => true;
@@ -196,6 +202,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
196202
webRunHeadless: featureFlags.isWebEnabled && boolArg('web-run-headless'),
197203
webBrowserDebugPort: browserDebugPort,
198204
enableImpeller: enableImpeller,
205+
uninstallFirst: uninstallFirst,
199206
);
200207
} else {
201208
return DebuggingOptions.enabled(
@@ -240,6 +247,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
240247
nullAssertions: boolArg('null-assertions'),
241248
nativeNullAssertions: boolArg('native-null-assertions'),
242249
enableImpeller: enableImpeller,
250+
uninstallFirst: uninstallFirst,
243251
);
244252
}
245253
}

packages/flutter_tools/lib/src/device.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,7 @@ class DebuggingOptions {
792792
this.nullAssertions = false,
793793
this.nativeNullAssertions = false,
794794
this.enableImpeller = false,
795+
this.uninstallFirst = false,
795796
}) : debuggingEnabled = true;
796797

797798
DebuggingOptions.disabled(this.buildInfo, {
@@ -808,6 +809,7 @@ class DebuggingOptions {
808809
this.cacheSkSL = false,
809810
this.traceAllowlist,
810811
this.enableImpeller = false,
812+
this.uninstallFirst = false,
811813
}) : debuggingEnabled = false,
812814
useTestFonts = false,
813815
startPaused = false,
@@ -876,6 +878,7 @@ class DebuggingOptions {
876878
required this.nullAssertions,
877879
required this.nativeNullAssertions,
878880
required this.enableImpeller,
881+
required this.uninstallFirst,
879882
});
880883

881884
final bool debuggingEnabled;
@@ -912,6 +915,11 @@ class DebuggingOptions {
912915
final bool webUseSseForInjectedClient;
913916
final bool enableImpeller;
914917

918+
/// Whether the tool should try to uninstall a previously installed version of the app.
919+
///
920+
/// This is not implemented for every platform.
921+
final bool uninstallFirst;
922+
915923
/// Whether to run the browser in headless mode.
916924
///
917925
/// Some CI environments do not provide a display and fail to launch the
@@ -1026,6 +1034,7 @@ class DebuggingOptions {
10261034
nullAssertions: (json['nullAssertions'] as bool?)!,
10271035
nativeNullAssertions: (json['nativeNullAssertions'] as bool?)!,
10281036
enableImpeller: (json['enableImpeller'] as bool?) ?? false,
1037+
uninstallFirst: (json['uninstallFirst'] as bool?) ?? false,
10291038
);
10301039
}
10311040

packages/flutter_tools/lib/src/ios/devices.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,7 @@ class IOSDevice extends Device {
394394
appDeltaDirectory: package.appDeltaDirectory,
395395
launchArguments: launchArguments,
396396
interfaceType: interfaceType,
397+
uninstallFirst: debuggingOptions.uninstallFirst,
397398
);
398399
if (deviceLogReader is IOSDeviceLogReader) {
399400
deviceLogReader.debuggerStream = iosDeployDebugger;
@@ -415,6 +416,7 @@ class IOSDevice extends Device {
415416
appDeltaDirectory: package.appDeltaDirectory,
416417
launchArguments: launchArguments,
417418
interfaceType: interfaceType,
419+
uninstallFirst: debuggingOptions.uninstallFirst,
418420
);
419421
} else {
420422
installationResult = await iosDeployDebugger!.launchAndAttach() ? 0 : 1;

packages/flutter_tools/lib/src/ios/ios_deploy.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ class IOSDeploy {
127127
required List<String> launchArguments,
128128
required IOSDeviceConnectionInterface interfaceType,
129129
Directory? appDeltaDirectory,
130+
required bool uninstallFirst,
130131
}) {
131132
appDeltaDirectory?.createSync(recursive: true);
132133
// Interactive debug session to support sending the lldb detach command.
@@ -144,6 +145,8 @@ class IOSDeploy {
144145
'--app_deltas',
145146
appDeltaDirectory.path,
146147
],
148+
if (uninstallFirst)
149+
'--uninstall',
147150
'--debug',
148151
if (interfaceType != IOSDeviceConnectionInterface.network)
149152
'--no-wifi',
@@ -168,6 +171,7 @@ class IOSDeploy {
168171
required String bundlePath,
169172
required List<String> launchArguments,
170173
required IOSDeviceConnectionInterface interfaceType,
174+
required bool uninstallFirst,
171175
Directory? appDeltaDirectory,
172176
}) async {
173177
appDeltaDirectory?.createSync(recursive: true);
@@ -183,6 +187,8 @@ class IOSDeploy {
183187
],
184188
if (interfaceType != IOSDeviceConnectionInterface.network)
185189
'--no-wifi',
190+
if (uninstallFirst)
191+
'--uninstall',
186192
'--justlaunch',
187193
if (launchArguments.isNotEmpty) ...<String>[
188194
'--args',

packages/flutter_tools/test/commands.shard/hermetic/run_test.dart

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,42 @@ void main() {
369369
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
370370
});
371371

372+
testUsingContext('forwards --uninstall-only to DebuggingOptions', () async {
373+
final RunCommand command = RunCommand();
374+
final FakeDevice mockDevice = FakeDevice(
375+
sdkNameAndVersion: 'iOS 13',
376+
)..startAppSuccess = false;
377+
378+
mockDeviceManager
379+
..devices = <Device>[
380+
mockDevice,
381+
]
382+
..targetDevices = <Device>[
383+
mockDevice,
384+
];
385+
386+
// Causes swift to be detected in the analytics.
387+
fs.currentDirectory.childDirectory('ios').childFile('AppDelegate.swift').createSync(recursive: true);
388+
389+
await expectToolExitLater(createTestCommandRunner(command).run(<String>[
390+
'run',
391+
'--no-pub',
392+
'--no-hot',
393+
'--uninstall-first',
394+
]), isNull);
395+
396+
final DebuggingOptions options = await command.createDebuggingOptions(false);
397+
expect(options.uninstallFirst, isTrue);
398+
}, overrides: <Type, Generator>{
399+
Artifacts: () => artifacts,
400+
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
401+
DeviceManager: () => mockDeviceManager,
402+
FileSystem: () => fs,
403+
ProcessManager: () => FakeProcessManager.any(),
404+
Usage: () => usage,
405+
});
406+
407+
372408
testUsingContext('passes device target platform to usage', () async {
373409
final RunCommand command = RunCommand();
374410
final FakeDevice mockDevice = FakeDevice(sdkNameAndVersion: 'iOS 13')
@@ -407,7 +443,7 @@ void main() {
407443
Usage: () => usage,
408444
});
409445

410-
group('--machine', () {
446+
group('--machine', () {
411447
testUsingContext('enables multidex by default', () async {
412448
final DaemonCapturingRunCommand command = DaemonCapturingRunCommand();
413449
final FakeDevice device = FakeDevice();

packages/flutter_tools/test/general.shard/ios/ios_deploy_test.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ void main () {
5353
'/',
5454
'--app_deltas',
5555
'app-delta',
56+
'--uninstall',
5657
'--debug',
5758
'--args',
5859
<String>[
@@ -73,6 +74,7 @@ void main () {
7374
appDeltaDirectory: appDeltaDirectory,
7475
launchArguments: <String>['--enable-dart-profiling'],
7576
interfaceType: IOSDeviceConnectionInterface.network,
77+
uninstallFirst: true,
7678
);
7779

7880
expect(iosDeployDebugger.logLines, emits('Did finish launching.'));

0 commit comments

Comments
 (0)