Skip to content

Commit 14546bf

Browse files
authored
Support uninstall, install status query for UWP (flutter#82481)
Adds UwpTool.install and UwpTool.uninstall methods. Refactors the PowerShell-based install code to move the powershell-related bits out of the Device class and into UwpTool so that when we swap out the PowerShell-based install for the uwptool-based install, it's transparent to the WindowsUWPDevice class. Adds implementations for: * WindowsUWPDevice.isAppInstalled * WindowsUWPDevice.uninstallApp Refactors: * WindowsUWPDevice.installApp
1 parent 1af31d8 commit 14546bf

File tree

4 files changed

+123
-68
lines changed

4 files changed

+123
-68
lines changed

packages/flutter_tools/lib/src/context_runner.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ Future<T> runInContext<T>(
209209
),
210210
uwptool: UwpTool(
211211
artifacts: globals.artifacts,
212+
fileSystem: globals.fs,
212213
logger: globals.logger,
213214
processManager: globals.processManager,
214215
),

packages/flutter_tools/lib/src/windows/uwptool.dart

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'package:meta/meta.dart';
1010
import 'package:process/process.dart';
1111

1212
import '../artifacts.dart';
13+
import '../base/file_system.dart';
1314
import '../base/logger.dart';
1415
import '../base/process.dart';
1516

@@ -22,13 +23,16 @@ import '../base/process.dart';
2223
class UwpTool {
2324
UwpTool({
2425
@required Artifacts artifacts,
26+
@required FileSystem fileSystem,
2527
@required Logger logger,
2628
@required ProcessManager processManager,
2729
}) : _artifacts = artifacts,
30+
_fileSystem = fileSystem,
2831
_logger = logger,
2932
_processUtils = ProcessUtils(processManager: processManager, logger: logger);
3033

3134
final Artifacts _artifacts;
35+
final FileSystem _fileSystem;
3236
final Logger _logger;
3337
final ProcessUtils _processUtils;
3438

@@ -44,44 +48,74 @@ class UwpTool {
4448
_logger.printError('Failed to list installed UWP apps: ${result.stderr}');
4549
return <String>[];
4650
}
47-
final List<String> appIds = <String>[];
51+
final List<String> packageFamilies = <String>[];
4852
for (final String line in result.stdout.toString().split('\n')) {
49-
final String appId = line.trim();
50-
if (appId.isNotEmpty) {
51-
appIds.add(appId);
53+
final String packageFamily = line.trim();
54+
if (packageFamily.isNotEmpty) {
55+
packageFamilies.add(packageFamily);
5256
}
5357
}
54-
return appIds;
58+
return packageFamilies;
5559
}
5660

57-
/// Returns the app ID for the specified package ID.
61+
/// Returns the package family name for the specified package name.
5862
///
59-
/// If no installed application on the system matches the specified GUID,
60-
/// returns null.
61-
Future<String/*?*/> getAppIdFromPackageId(String packageId) async {
62-
for (final String appId in await listApps()) {
63-
if (appId.startsWith(packageId)) {
64-
return appId;
63+
/// If no installed application on the system matches the specified package
64+
/// name, returns null.
65+
Future<String/*?*/> getPackageFamilyName(String packageName) async {
66+
for (final String packageFamily in await listApps()) {
67+
if (packageFamily.startsWith(packageName)) {
68+
return packageFamily;
6569
}
6670
}
6771
return null;
6872
}
6973

70-
/// Launches the app with the specified app ID.
74+
/// Launches the app with the specified package family name.
7175
///
7276
/// On success, returns the process ID of the launched app, otherwise null.
73-
Future<int/*?*/> launchApp(String appId, List<String> args) async {
77+
Future<int/*?*/> launchApp(String packageFamily, List<String> args) async {
7478
final List<String> launchCommand = <String>[
7579
_binaryPath,
7680
'launch',
77-
appId
81+
packageFamily
7882
] + args;
7983
final RunResult result = await _processUtils.run(launchCommand);
8084
if (result.exitCode != 0) {
81-
_logger.printError('Failed to launch app $appId: ${result.stderr}');
85+
_logger.printError('Failed to launch app $packageFamily: ${result.stderr}');
8286
return null;
8387
}
8488
// Read the process ID from stdout.
8589
return int.tryParse(result.stdout.toString().trim());
8690
}
91+
92+
/// Installs the app with the specified build directory.
93+
///
94+
/// Returns `true` on success.
95+
Future<bool> installApp(String buildDirectory) async {
96+
final List<String> launchCommand = <String>[
97+
'powershell.exe',
98+
_fileSystem.path.join(buildDirectory, 'install.ps1'),
99+
];
100+
final RunResult result = await _processUtils.run(launchCommand);
101+
if (result.exitCode != 0) {
102+
_logger.printError(result.stdout.toString());
103+
_logger.printError(result.stderr.toString());
104+
}
105+
return result.exitCode == 0;
106+
}
107+
108+
Future<bool> uninstallApp(String packageFamily) async {
109+
final List<String> launchCommand = <String>[
110+
_binaryPath,
111+
'uninstall',
112+
packageFamily
113+
];
114+
final RunResult result = await _processUtils.run(launchCommand);
115+
if (result.exitCode != 0) {
116+
_logger.printError('Failed to uninstall $packageFamily');
117+
return false;
118+
}
119+
return true;
120+
}
87121
}

packages/flutter_tools/lib/src/windows/windows_device.dart

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import 'package:process/process.dart';
1111

1212
import '../application_package.dart';
1313
import '../base/file_system.dart';
14-
import '../base/io.dart';
1514
import '../base/logger.dart';
1615
import '../base/os.dart';
1716
import '../base/utils.dart';
@@ -144,19 +143,16 @@ class WindowsUWPDevice extends Device {
144143
}
145144
final String config = toTitleCase(getNameForBuildMode(_buildMode ?? BuildMode.debug));
146145
final String generated = '${binaryName}_${packageVersion}_${config}_Test';
147-
final ProcessResult result = await _processManager.run(<String>[
148-
'powershell.exe',
149-
_fileSystem.path.join('build', 'winuwp', 'runner_uwp', 'AppPackages', binaryName, generated, 'install.ps1'),
150-
]);
151-
if (result.exitCode != 0) {
152-
_logger.printError(result.stdout.toString());
153-
_logger.printError(result.stderr.toString());
154-
}
155-
return result.exitCode == 0;
146+
final String buildDirectory = _fileSystem.path.join(
147+
'build', 'winuwp', 'runner_uwp', 'AppPackages', binaryName, generated);
148+
return _uwptool.installApp(buildDirectory);
156149
}
157150

158151
@override
159-
Future<bool> isAppInstalled(covariant ApplicationPackage app, {String userIdentifier}) async => false;
152+
Future<bool> isAppInstalled(covariant ApplicationPackage app, {String userIdentifier}) async {
153+
final String packageName = app.id;
154+
return await _uwptool.getPackageFamilyName(packageName) != null;
155+
}
160156

161157
@override
162158
Future<bool> isLatestBuildInstalled(covariant ApplicationPackage app) async => false;
@@ -193,24 +189,24 @@ class WindowsUWPDevice extends Device {
193189
return LaunchResult.failed();
194190
}
195191

196-
final String guid = package.id;
197-
if (guid == null) {
192+
final String packageName = package.id;
193+
if (packageName == null) {
198194
_logger.printError('Could not find PACKAGE_GUID in ${package.project.runnerCmakeFile.path}');
199195
return LaunchResult.failed();
200196
}
201197

202-
final String appId = await _uwptool.getAppIdFromPackageId(guid);
198+
final String packageFamily = await _uwptool.getPackageFamilyName(packageName);
203199

204200
if (debuggingOptions.buildInfo.mode.isRelease) {
205-
_processId = await _uwptool.launchApp(appId, <String>[]);
201+
_processId = await _uwptool.launchApp(packageFamily, <String>[]);
206202
return _processId != null ? LaunchResult.succeeded() : LaunchResult.failed();
207203
}
208204

209205
/// If the terminal is attached, prompt the user to open the firewall port.
210206
if (_logger.terminal.stdinHasTerminal) {
211207
await _logger.terminal.promptForCharInput(<String>['Y', 'y'], logger: _logger,
212208
prompt: 'To continue start an admin cmd prompt and run the following command:\n'
213-
' checknetisolation loopbackexempt -is -n=$appId\n'
209+
' checknetisolation loopbackexempt -is -n=$packageFamily\n'
214210
'Press "Y/y" once this is complete.'
215211
);
216212
}
@@ -238,7 +234,7 @@ class WindowsUWPDevice extends Device {
238234
if (debuggingOptions.purgePersistentCache) '--purge-persistent-cache',
239235
if (platformArgs['trace-startup'] as bool ?? false) '--trace-startup',
240236
];
241-
_processId = await _uwptool.launchApp(appId, args);
237+
_processId = await _uwptool.launchApp(packageFamily, args);
242238
if (_processId == null) {
243239
return LaunchResult.failed();
244240
}
@@ -255,7 +251,17 @@ class WindowsUWPDevice extends Device {
255251

256252
@override
257253
Future<bool> uninstallApp(covariant BuildableUwpApp app, {String userIdentifier}) async {
258-
return false;
254+
final String packageName = app.id;
255+
if (packageName == null) {
256+
_logger.printError('Could not find PACKAGE_GUID in ${app.project.runnerCmakeFile.path}');
257+
return false;
258+
}
259+
final String packageFamily = await _uwptool.getPackageFamilyName(packageName);
260+
if (packageFamily == null) {
261+
// App is not installed.
262+
return true;
263+
}
264+
return _uwptool.uninstallApp(packageFamily);
259265
}
260266

261267
@override

packages/flutter_tools/test/general.shard/windows/windows_device_test.dart

Lines changed: 48 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,14 @@ void main() {
4343
});
4444

4545
testWithoutContext('WindowsUwpDevice defaults', () async {
46-
final WindowsUWPDevice windowsDevice = setUpWindowsUwpDevice();
46+
final FakeUwpTool uwptool = FakeUwpTool();
47+
final WindowsUWPDevice windowsDevice = setUpWindowsUwpDevice(uwptool: uwptool);
4748
final FakeBuildableUwpApp package = FakeBuildableUwpApp();
4849

4950
expect(await windowsDevice.targetPlatform, TargetPlatform.windows_uwp_x64);
5051
expect(windowsDevice.name, 'Windows (UWP)');
5152
expect(await windowsDevice.installApp(package), true);
52-
expect(await windowsDevice.uninstallApp(package), false);
53+
expect(await windowsDevice.uninstallApp(package), true);
5354
expect(await windowsDevice.isLatestBuildInstalled(package), false);
5455
expect(await windowsDevice.isAppInstalled(package), false);
5556
expect(windowsDevice.category, Category.desktop);
@@ -170,15 +171,8 @@ void main() {
170171
Cache.flutterRoot = '';
171172
final FakeUwpTool uwptool = FakeUwpTool();
172173
final FileSystem fileSystem = MemoryFileSystem.test();
173-
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
174-
const FakeCommand(command: <String>[
175-
'powershell.exe',
176-
'build/winuwp/runner_uwp/AppPackages/testapp/testapp_1.2.3.4_Debug_Test/install.ps1',
177-
]),
178-
]);
179174
final WindowsUWPDevice windowsDevice = setUpWindowsUwpDevice(
180175
fileSystem: fileSystem,
181-
processManager: processManager,
182176
uwptool: uwptool,
183177
);
184178
final FakeBuildableUwpApp package = FakeBuildableUwpApp();
@@ -191,7 +185,7 @@ void main() {
191185
);
192186

193187
expect(result.started, true);
194-
expect(uwptool.launchRequests.single.appId, 'PACKAGE-ID_asdfghjkl');
188+
expect(uwptool.launchRequests.single.packageFamily, 'PACKAGE-ID_publisher');
195189
expect(uwptool.launchRequests.single.args, <String>[
196190
'--observatory-port=12345',
197191
'--disable-service-auth-codes',
@@ -205,15 +199,8 @@ void main() {
205199
Cache.flutterRoot = '';
206200
final FakeUwpTool uwptool = FakeUwpTool();
207201
final FileSystem fileSystem = MemoryFileSystem.test();
208-
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
209-
const FakeCommand(command: <String>[
210-
'powershell.exe',
211-
'build/winuwp/runner_uwp/AppPackages/testapp/testapp_1.2.3.4_Release_Test/install.ps1',
212-
]),
213-
]);
214202
final WindowsUWPDevice windowsDevice = setUpWindowsUwpDevice(
215203
fileSystem: fileSystem,
216-
processManager: processManager,
217204
uwptool: uwptool,
218205
);
219206
final FakeBuildableUwpApp package = FakeBuildableUwpApp();
@@ -226,7 +213,7 @@ void main() {
226213
);
227214

228215
expect(result.started, true);
229-
expect(uwptool.launchRequests.single.appId, 'PACKAGE-ID_asdfghjkl');
216+
expect(uwptool.launchRequests.single.packageFamily, 'PACKAGE-ID_publisher');
230217
expect(uwptool.launchRequests.single.args, <String>[]);
231218
});
232219
}
@@ -279,44 +266,71 @@ class FakeBuildableUwpApp extends Fake implements BuildableUwpApp {
279266
String get name => 'testapp';
280267
@override
281268
String get projectVersion => '1.2.3.4';
269+
270+
// Test helper to get the expected package family.
271+
static const String packageFamily = 'PACKAGE-ID_publisher';
282272
}
283273

284274
class FakeUwpTool implements UwpTool {
275+
bool isInstalled = false;
276+
final List<_GetPackageFamilyRequest> getPackageFamilyRequests = <_GetPackageFamilyRequest>[];
285277
final List<_LaunchRequest> launchRequests = <_LaunchRequest>[];
286-
final List<_LookupAppIdRequest> lookupAppIdRequests = <_LookupAppIdRequest>[];
278+
final List<_InstallRequest> installRequests = <_InstallRequest>[];
279+
final List<_UninstallRequest> uninstallRequests = <_UninstallRequest>[];
287280

288281
@override
289282
Future<List<String>> listApps() async {
290-
return <String>[
291-
'fb89bf4f-55db-4bcd-8f0b-d8139953e08b',
292-
'3e556a66-cb7f-4335-9569-35d5f5e37219',
293-
'dfe5d409-a524-4635-b2f8-78a5e9551994',
294-
'51e8a06b-02e8-4f76-9131-f20ce114fc34',
295-
];
283+
return isInstalled ? <String>[FakeBuildableUwpApp.packageFamily] : <String>[];
296284
}
297285

298286
@override
299-
Future<String> getAppIdFromPackageId(String packageId) async {
300-
lookupAppIdRequests.add(_LookupAppIdRequest(packageId));
301-
return '${packageId}_asdfghjkl';
287+
Future<String/*?*/> getPackageFamilyName(String packageName) async {
288+
getPackageFamilyRequests.add(_GetPackageFamilyRequest(packageName));
289+
return isInstalled ? FakeBuildableUwpApp.packageFamily : null;
302290
}
303291

304292
@override
305-
Future<int> launchApp(String appId, List<String> args) async {
306-
launchRequests.add(_LaunchRequest(appId, args));
293+
Future<int/*?*/> launchApp(String packageFamily, List<String> args) async {
294+
launchRequests.add(_LaunchRequest(packageFamily, args));
307295
return 42;
308296
}
297+
298+
@override
299+
Future<bool> installApp(String buildDirectory) async {
300+
installRequests.add(_InstallRequest(buildDirectory));
301+
isInstalled = true;
302+
return true;
303+
}
304+
305+
@override
306+
Future<bool> uninstallApp(String packageFamily) async {
307+
uninstallRequests.add(_UninstallRequest(packageFamily));
308+
isInstalled = false;
309+
return true;
310+
}
309311
}
310312

311-
class _LookupAppIdRequest {
312-
const _LookupAppIdRequest(this.packageId);
313+
class _GetPackageFamilyRequest {
314+
const _GetPackageFamilyRequest(this.packageId);
313315

314316
final String packageId;
315317
}
316318

317319
class _LaunchRequest {
318-
const _LaunchRequest(this.appId, this.args);
320+
const _LaunchRequest(this.packageFamily, this.args);
319321

320-
final String appId;
322+
final String packageFamily;
321323
final List<String> args;
322324
}
325+
326+
class _InstallRequest {
327+
const _InstallRequest(this.buildDirectory);
328+
329+
final String buildDirectory;
330+
}
331+
332+
class _UninstallRequest {
333+
const _UninstallRequest(this.packageFamily);
334+
335+
final String packageFamily;
336+
}

0 commit comments

Comments
 (0)